注解和反射

95 阅读4分钟

注解

  • 注解是放在Java源码的类、方法、字段、参数前的一种特殊“注释”:
  • 注释会被编译器直接忽略,注解则可以被编译器打包进入class文件,因此,注解是一种用作标注的“元数据”。
  • 可以用default设定一个默认值(强烈推荐)。
  • 最常用的参数应当命名为value
  • SOURCE类型的注解在编译期就被丢掉了;
  • CLASS类型的注解仅保存在class文件中,它们不会被加载进JVM;
  • RUNTIME类型的注解会被加载进JVM,并且在运行期可以被程序读取。
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {

    String value() default "";

    String type() default "";

}

配置参数

  • 所有基本类型;
  • String;
  • 枚举类型;
  • 基本类型、String、Class以及枚举的数组。

元注解

@Target

定义Annotation能够被应用于源码的哪些位置

  • 类或接口:ElementType.TYPE
  • 字段:ElementType.FIELD
  • 方法:ElementType.METHOD
  • 构造方法:ElementType.CONSTRUCTOR
  • 方法参数:ElementType.PARAMETER

@Retention

定义了Annotation的生命周期

  • 仅编译期:RetentionPolicy.SOURCE
  • 仅class文件:RetentionPolicy.CLASS
  • 运行期:RetentionPolicy.RUNTIME

@Retention不存在,则该Annotation默认为CLASS

@Inherited

义子类是否可继承父类定义的Annotation@Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效,并且仅针对class的继承,对interface的继承无效:

反射

Class

获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。

方法一:直接通过一个class的静态变量class获取:

Class cls = String.class;

方法二:如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取

String s = "Hello";
Class cls = s.getClass();

方法三:如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:

Class cls = Class.forName("java.lang.String");

instanceof不但匹配指定类型,还匹配指定类型的子类。而用==判断class实例可以精确地判断数据类型,但不能作子类型比较。

数组(例如String[])也是一种类,而且不同于String.class,它的类名是[Ljava.lang.String;

获取到了一个Class实例,

// 获取StringClass实例:
Class cls = String.class;
// 创建一个String实例:
String s = (String) cls.newInstance();

Class.newInstance()可以创建类实例,它的局限是:只能调用public的无参数构造方法。带参数的构造方法,或者非public的构造方法都无法通过Class.newInstance()被调用。

动态加载

JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。

访问字段

  • Field getField(name):根据字段名获取某个public的field(包括父类)
  • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
  • Field[] getFields():获取所有public的field(包括父类)
  • Field[] getDeclaredFields():获取当前类的所有field(不包括父类)

一个Field对象包含了一个字段的所有信息:

  • getName():返回字段名称,例如,"name"
  • getType():返回字段类型,也是一个Class实例,例如,String.class
  • getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义。
  • Field.get(Object)获取指定实例的指定字段的值。
  • Field.setAccessible(true) 放开权限
  • Field.set(Object, Object)设置字段的值
BaseUser user = new BaseUser();
user.setId("123");
Class c = user.getClass();
Field field = c.getDeclaredField("id");
// 翻開權限
field.setAccessible(true);
System.out.println(field.get(user));

调用方法

获取Method

-   `Method getMethod(name, Class...)`:获取某个`public``Method`(包括父类)
-   `Method getDeclaredMethod(name, Class...)`:获取当前类的某个`Method`(不包括父类)
-   `Method[] getMethods()`:获取所有`public``Method`(包括父类)
-   `Method[] getDeclaredMethods()`:获取当前类的所有`Method`(不包括父类)

一个Method对象包含一个方法的所有信息:

一个`Method`对象包含一个方法的所有信息:
-   `getName()`:返回方法名称,例如:`"getScore"`-   `getReturnType()`:返回方法返回值类型,也是一个Class实例,例如:`String.class`-   `getParameterTypes()`:返回方法的参数类型,是一个Class数组,例如:`{String.class, int.class}`-   `getModifiers()`:返回方法的修饰符,它是一个`int`,不同的bit表示不同的含义。

方法调用

invoke 对Method实例调用invoke就相当于调用该方法,invoke的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错。

BaseUserController baseUserController = new BaseUserController();
Method method = BaseUserController.class.getMethod("consumeMsg",Map.class);
Map<String,Object> map = new HashMap<>();
map.put("123","23");
method.invoke(baseUserController,map);
  • 调用静态方法时,由于无需指定实例对象,所以invoke方法传入的第一个参数永远为null

  • 调用非public方法,通过Method.setAccessible(true)允许其调用:

  • 使用反射调用方法时,仍然遵循多态原则:即总是调用实际类型的覆写方法(如果存在)

调用构造方法

-   `getConstructor(Class...)`:获取某个`public``Constructor`-   `getDeclaredConstructor(Class...)`:获取某个`Constructor`-   `getConstructors()`:获取所有`public``Constructor`-   `getDeclaredConstructors()`:获取所有`Constructor`

调用非publicConstructor时,必须首先通过setAccessible(true)设置允许访问。setAccessible(true)可能会失败。

获取继承关系

获取父类

  • Class getSuperclass():获取父类类型;

获取interface

  • Class[] getInterfaces():获取当前类实现的所有接口。