注解
- 注解是放在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实例,
// 获取String的Class实例:
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`。
调用非public的Constructor时,必须首先通过setAccessible(true)设置允许访问。setAccessible(true)可能会失败。
获取继承关系
获取父类
Class getSuperclass():获取父类类型;
获取interface
Class[] getInterfaces():获取当前类实现的所有接口。