Java注解和反射

注解

什么是注解

  • Annotation 是从 JDK5.0 开始引入的新技术

  • Annotation 的作用:

    • 不是程序本身,可以对程序作出解释。(这一点和注释 (comment) 没什么区别)
    • 可以被其他程序 (比如: 编译器等) 读取。
  • Annotation 的格式:

    • 注解是以 "@注释名" 在代码中存在的,还可以添加一些参数值,例如:@SuppressWarnings(value="unchecked")。
  • Annotation 在哪里使用?

    • 可以附加在 package、class、method、field 等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问

内置注解

  • @Override: 定义在 java.lang.Override 中,此注解只适用于修饰方法,表示一个方法声明打算重写超类中的另一个方法声明
  • @Deprecated: 定义在 java.lang.Deprecated 中,此注解可以用于修饰方法,属性,类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或者存在更好的选择
  • @SuppressWarnings: 定义在 java.lang.SuppressWarnings 中,用来抑制编译时的警告信息
    • 与前两个注解有所不同,你需要添加一个参数才能正确使用,这些参数都是已经定义好的,我们选择性的使用就好了。
    • @SuppressWarnings("all")
    • @SuppressWarnings("unchecked")\
    • SuppressWarnings(value={"unchecked","deprecation"})
    • 等等......

元注解

  • 元注解的作用就是负责注解其他注解,Java 定义了 4 个标准的 meta_annotation 类型,他们被用来提供对其他 annotation 类型作用说明。
  • 这些类型和它们锁支持的类在 java.lang.annotation 包中可以找到.(@Target,@Retention,@Documented,@Inherited)
    • @Target: 用于描述注解的使用范围 (即:被描述的注解可以使用在什么地方)
    • @Retention: 表示需要在什么级别保存在注释信息,用于描述注解的生命周期
      • (SOURCE < CLASS < RUNTIME)
    • @Document: 说明该注解被包含在 javadoc 中
    • @Inherited: 说明子类可以集成父类中的该注解

自定义注解

  • 使用 @interface 自定义注解时, 自动继承了 java.lang.annotation.Annotation 接口
  • 分析:
    • @interface 用来声明一个注解,格式: public @ interface 注解名 {定义内容}
    • 其中的每个方法实际上是声明了一个配置参数
    • 方法的名称就是参数的名称
    • 返回值类型就是参数的类型 (返回值只能是基本类型,Class,String,enum)
    • 可以通过 default 来声明参数的默认值
    • 如果只有一个参数成员,一般参数名为 value
    • 注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0 作为默认值
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
// 注解的参数: 参数类型 + 参数名 ()
String name() default "";
int age() default 0;
int id();
String[] schools() default {"清华大学", "北京大学"};
}

class Test {
// 如果没有默认值,我们就必须给注解赋值
@MyAnnotation(id = 2006)
public void test() {
}
}


反射

反射机制的概述

动态语言

  • 是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被杉树或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。
  • 主要动态语言:Object-C、C#、JavaScript、PHP、Python

静态语言

  • 与动态语言相对应的,运行时结构不可变的语言就是静态语言。如:Java、C、C++

  • Java 不是动态语言,但 Java 可以称之为 "准动态语言"。即 Java 有一定的动态性,我们可以利用反射机制获取类似动态语言的特性。Java 的动态性让编程的时候更加灵活!

  • Reflection(反射) 是 Java 被视为动态语言的关键,反射机制运行程序在执行期借助于 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

Class c = Class.forName("java.lang.String")

  • 加载完类之后,在堆内存的方法区中就产生了一个 Class 类型的对象 (一个类只能有一个 Class 对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到的类的结构,所以,我们形象的称之为:反射

Java 反射机制提供的功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时处理注解
  • 生成动态代理
  • ......

Java 反射优点和缺点

  • 优点
    • 可以实现动态创建对象和编译,体现出很大的灵活性
  • 缺点
    • 对性能有影响。使用反射基本上是一种解释操作,我们可以告诉 JVM,我们希望做什么并且满足我们的要求。这类操作总是慢于直接执行相同的操作。

反射相关的主要 API

  • java.lang.Class: 代表一个类
  • java.lang.reflect.Methode: 代表类的方法
  • java.lang.reflect.Field: 代表类的成员变量
  • java.lang.reflect.Constructor: 代表类的构造器
  • ...... .......
package com.ycy.demo02;

/**

  • @author YeCaiYu

  • @date 2020-05-04 9:43
    */
    public class Test02 {
    public static void main(String[] args) throws ClassNotFoundException {
    // 通过反射获取类的 Class 对象
    Class c1 = Class.forName("com.ycy.demo02.User");

     System.out.println(c1);
    
     <span class="hljs-type">Class</span> <span class="hljs-variable">c2</span> <span class="hljs-operator">=</span> Class.forName(<span class="hljs-string">"com.ycy.demo02.User"</span>);
     <span class="hljs-type">Class</span> <span class="hljs-variable">c3</span> <span class="hljs-operator">=</span> Class.forName(<span class="hljs-string">"com.ycy.demo02.User"</span>);
     <span class="hljs-type">Class</span> <span class="hljs-variable">c4</span> <span class="hljs-operator">=</span> Class.forName(<span class="hljs-string">"com.ycy.demo02.User"</span>);
     <span class="hljs-comment">//一个类在内存中只有一个Class对象</span>
     <span class="hljs-comment">//一个类被加载后,类的整个结构都会被封装在Class对象中</span>
     System.out.println(c1.hashCode());
     System.out.println(c2.hashCode());
     System.out.println(c3.hashCode());
     System.out.println(c4.hashCode());
    

    }
    }

// 实体类:pojo , entity
class User {
private String name;
private int id;
private int age;

<span class="hljs-keyword">public</span> <span class="hljs-title function_">User</span><span class="hljs-params">()</span> {
}

<span class="hljs-keyword">public</span> <span class="hljs-title function_">User</span><span class="hljs-params">(String name, <span class="hljs-type">int</span> id, <span class="hljs-type">int</span> age)</span> {
    <span class="hljs-built_in">this</span>.name = name;
    <span class="hljs-built_in">this</span>.id = id;
    <span class="hljs-built_in">this</span>.age = age;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> String <span class="hljs-title function_">toString</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-string">"User{"</span> +
            <span class="hljs-string">"name='"</span> + name + <span class="hljs-string">'\''</span> +
            <span class="hljs-string">", id="</span> + id +
            <span class="hljs-string">", age="</span> + age +
            <span class="hljs-string">'}'</span>;
}

<span class="hljs-keyword">public</span> String <span class="hljs-title function_">getName</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> name;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setName</span><span class="hljs-params">(String name)</span> {
    <span class="hljs-built_in">this</span>.name = name;
}

<span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">getId</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> id;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setId</span><span class="hljs-params">(<span class="hljs-type">int</span> id)</span> {
    <span class="hljs-built_in">this</span>.id = id;
}

<span class="hljs-keyword">public</span> <span class="hljs-type">int</span> <span class="hljs-title function_">getAge</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> age;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">setAge</span><span class="hljs-params">(<span class="hljs-type">int</span> age)</span> {
    <span class="hljs-built_in">this</span>.age = age;
}

}

Class 类

在 Object 类中定义了以下的方法,此方法被所有子类集成

public final Class getClass()

  • 以上的方法返回值的类型是一个 Class 类,此类是 Java 反射的源头,实际上所谓的反射从程序的运行结果来看也是很好理解,即:可以通过对象反射求出类的名称。

对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了那些接口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个结构 (class/interface/enum/annotation/primitive type/void/[]) 的有关信息。

  • Class 本身也是一个类
  • Class 对象只能由系统建立对象
  • 一个加载的类在 JVM 中只会有一个 Class 实例
  • 一个 Class 对象对应的是一个加载到 JJVM 中的一个.class 文件
  • 每个类的实例都会记得自己是由哪个 Class 实例所生成
  • 通过 Class 可以完整地得到一个类中所有被加载的结构
  • Class 类是 Reflection 的根源,针对任何你想动态加载、运行的类,唯独先获得相应 Class 对象
方法名 功能说明
static ClassforName(String name) 返回指定类名 name 的 Class 对象
Object newInstance() 调用缺省构造函数,返回 Class 对象的一个实例
getName() 返回此 Class 对象所表示的实体 (类、接口、数组类或 void) 的名称
Class getSuperClass() 返回当前 Class 对象的父类的 Class 对象
Class[] getinterfaces() 获取当前 Class 对象的接口
Constructor[] getConstructors() 返回一个包含某些 Constructor 对象的数组
ClassLoader getClassLoader() 返回该类的加载器
Method getMethod(String name.Class.. T) 返回一个 Method 对象,此对象的形参类型为 paramType
Field[] getDeclaredFields() 返回 Field 对象的一个数组
package com.ycy.demo02;

/**

  • @author YeCaiYu

  • @date 2020-05-04 10:05
    */
    // 测试 Class 类的创建有哪些
    public class Test03 {
    public static void main(String[] args) throws ClassNotFoundException {
    Person person = new Student();
    System.out.println("这个人是:" + person.name);

     <span class="hljs-comment">//方式一: 通用对象获取</span>
     <span class="hljs-type">Class</span> <span class="hljs-variable">c1</span> <span class="hljs-operator">=</span> person.getClass();
     System.out.println(c1.hashCode());
     <span class="hljs-comment">//方式二: forName获取</span>
     <span class="hljs-type">Class</span> <span class="hljs-variable">c2</span> <span class="hljs-operator">=</span> Class.forName(<span class="hljs-string">"com.ycy.demo02.Student"</span>);
     System.out.println(c2.hashCode());
     <span class="hljs-comment">//方式三: 通过类名.class获取</span>
     <span class="hljs-type">Class</span> <span class="hljs-variable">c3</span> <span class="hljs-operator">=</span> Student.class;
     System.out.println(c3.hashCode());
     <span class="hljs-comment">//方式四: 基本内置类型的包装类都有一个Type属性</span>
     <span class="hljs-type">Class</span> <span class="hljs-variable">c4</span> <span class="hljs-operator">=</span> Integer.TYPE;
     System.out.println(c4);
    
     <span class="hljs-comment">//获得父类类型</span>
     <span class="hljs-type">Class</span> <span class="hljs-variable">c5</span> <span class="hljs-operator">=</span> c1.getSuperclass();
     System.out.println(c5);
    

    }
    }

class Person {
public String name;

<span class="hljs-keyword">public</span> <span class="hljs-title function_">Person</span><span class="hljs-params">()</span> {
}

<span class="hljs-keyword">public</span> <span class="hljs-title function_">Person</span><span class="hljs-params">(String name)</span> {
    <span class="hljs-built_in">this</span>.name = name;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> String <span class="hljs-title function_">toString</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Person{"</span> +
            <span class="hljs-string">"name='"</span> + name + <span class="hljs-string">'\''</span> +
            <span class="hljs-string">'}'</span>;
}

}

class Student extends Person {
public Student() {
this.name = "学生";
}
}

class Teacher extends Person {
public Teacher() {
this.name = "老师";
}
}

类的加载


类初始化

  • 类的主动引用(一定会发生类的初始化)
    • 当虚拟机启动,先初始化 main 方法所在的类
    • new 一个类的对象
    • 调用类的静态成员(除 final 常量)和静态方法
    • 使用 java.lang.reflect 包的方法对类进行反射调用
    • 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
  • 类的被动引用(不会发生类的初始化)
    • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
    • 通过数组定义类的引用,不会触发此类的初始化
    • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)

获取类的运行时结构

package com.ycy.demo02;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
 * @author YeCaiYu
 * @date 2020-05-04 10:54
 */
public class Test05 {
    // 获取类的信息
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class c1 = Class.forName("com.ycy.demo02.User");
    <span class="hljs-comment">//获取类的名字</span>
    System.out.println(c1.getName());<span class="hljs-comment">//获取包名 + 类名</span>
    System.out.println(c1.getSimpleName()); <span class="hljs-comment">//获取类名</span>

    <span class="hljs-comment">//获取类的属性</span>
    Field[] fields = c1.getFields();<span class="hljs-comment">//只能找到public属性</span>
    <span class="hljs-keyword">for</span> (Field field : fields) {
        System.out.println(field);
    }
    System.out.println(<span class="hljs-string">"++++++++++++++++++++++"</span>);
    fields = c1.getDeclaredFields();<span class="hljs-comment">//能找到全部的属性</span>
    <span class="hljs-keyword">for</span> (Field field : fields) {
        System.out.println(field);
    }
    <span class="hljs-comment">//获取指定属性的值</span>
    <span class="hljs-type">Field</span> <span class="hljs-variable">name</span> <span class="hljs-operator">=</span> c1.getDeclaredField(<span class="hljs-string">"name"</span>);
    System.out.println(name);

    <span class="hljs-comment">//获取类的方法</span>
    System.out.println(<span class="hljs-string">"+++++++++++++++++++++++"</span>);
    Method[] methods = c1.getMethods();<span class="hljs-comment">//获得本类及其父类全部public方法</span>
    <span class="hljs-keyword">for</span> (Method method : methods) {
        System.out.println(method);
    }
    System.out.println(<span class="hljs-string">"+++++++++++++++++++++++"</span>);
    methods = c1.getDeclaredMethods();<span class="hljs-comment">//获取本类的所有方法</span>
    <span class="hljs-keyword">for</span> (Method method : methods) {
        System.out.println(method);
    }
    <span class="hljs-comment">//获得指定的方法</span>
    <span class="hljs-type">Method</span> <span class="hljs-variable">getName</span> <span class="hljs-operator">=</span> c1.getMethod(<span class="hljs-string">"getName"</span>, <span class="hljs-literal">null</span>);
    <span class="hljs-type">Method</span> <span class="hljs-variable">setName</span> <span class="hljs-operator">=</span> c1.getMethod(<span class="hljs-string">"setName"</span>, String.class);
    System.out.println(getName);
    System.out.println(setName);
    System.out.println(<span class="hljs-string">"********************************"</span>);
    <span class="hljs-comment">//获取指定的构造器</span>
    Constructor[] constructors = c1.getConstructors();
    <span class="hljs-keyword">for</span> (Constructor constructor : constructors) {
        System.out.println(constructor);
    }
    System.out.println(<span class="hljs-string">"********************************"</span>);
    constructors = c1.getDeclaredConstructors();
    <span class="hljs-keyword">for</span> (Constructor constructor : constructors) {
        System.out.println(constructor);
    }
    <span class="hljs-comment">//获得指定构造器</span>
    <span class="hljs-type">Constructor</span> <span class="hljs-variable">constructor</span> <span class="hljs-operator">=</span> c1.getConstructor(String.class, <span class="hljs-type">int</span>.class, <span class="hljs-type">int</span>.class);
    System.out.println(<span class="hljs-string">"指定:"</span> + constructor);

}

}

调用执行的方法

通过反射,调用类中的方法,通过 Method 类完成。

  • 通过 Class 类的 getMethod(String name,Class.. parameterTypes) 方法取得一个 Method 对象,并设置此方法操作时所需要的的参数类型。
  • 之后使用 Object invoke(Object obj,Object[] args) 进行调用,并向方法中传递设置的 obj 对象的参数信息

若原方法声明为 private,则需要在调用此 invoke()方法前,显式调用对象的 setAccessible(true) 方法,将可访问 private