一、反射
1. 反射机制
反射机制的相关类除了一个java.lang.Class,其余都在java.lang.reflect包下。
反射机制用于读取class字节码文件,需要注意,JVM加载字节码到内存中时都只会保存一份,多次读取class文件时不用担心也会加载多次。
反射机制相关的常用类:
- java.lang.Class:代表整个类的字节码,表示一个类型。
- java.lang.reflect.Method:代表字节码中的方法字节码,表示一个方法。
- java.lang.reflect.Constructor:代表字节码中的构造方法字节码,表示一个构造方法。
- java.lang.reflect.Field:代表字节码中的属性字节码,表示一个属性。
2. 反射类字节码Class(类/类型)
- 第一种方式:通过Class类的静态方法forName,例如
Class c1 = Class.forName("java.lang.String");
就表示获取到了String这个类的class字节码,注意,这是Class类下的一个静态方法,参数需要是完整的包名。另外,Class.forName方法的使用会导致类的加载,也就是说如果希望只是执行一个类的静态代码块,并不执行其他的代码,就可以使用这个方法来进行类的加载,此时,自然就会去执行对应的静态代码了。 - 第二种方式:通过Object类的getClass方法,例如
String s = "abc"; Class c2 = s.getClass();
,即通过任何类对象的getClass方法就可以拿到对应了类字节码了,并且因为JVM只会在内存中加载一份相同类的字节码,所以这个例子的c2和第一种方式的c1使用双等号判断返回结果是true。 - 第三种方式:通过类的class属性,例如
Class c3 = String.class;
Class中常用的方法:
- String getName():返回类的完整类名(包含包路径)。
- String getsimpleName():返回类的简类名(类定义名称)。
- Field[] getFields():获取Class中所有public类型的Field对象(属性)。
- Field[] getDeclaredFields():获取Class中所有的Field对象(属性)。
- Field getDeclaredField(String name):获取指定名称的Field对象(属性)。
- Method[] getDeclaredMethods():获取所有的Method对象(方法)。
- Method getDeclaredMethod(String name, Class>... parameterTypes):根据方法名称和参数类型列表获取Method对象(方法),例如“userClass.getDeclaredMethod("login", String.class, int.class);”。
- Constructor>[] getDeclaredConstructors():获取所有的Constructor对象(构造方法)。
- Constructor
getDeclaredConstructor(Class>... parameterTypes): 获取指定参数类型列表的Constructor对象(构造方法),例如“userClass.getDeclaredConstructor(String.class, int.class);”。 - Class super T> getSuperclass():获取类的父类。
- Class>[] getInterfaces():获取所有类实现的接口。
3. 反射属性字节码Field(字段/属性)
获取Field需要先获取到对应的类字节码Class,然后才能从类中获取到对应的Field(java.lang.reflect.Field)。
Field常用方法:
- Class> getType():返回属性的数据类型。
- String getName():返回属性的名称。
- int getModifiers():返回修饰符列表的代号,此代号可以使用java.lang.reflect.Modifier的静态方法toString方法传入代号获取到具体的修饰符名称列表。
- void set(Object obj, Object value):给指定对象的Field对象赋予值value。
- Object get(Object obj):获取指定对象的Field值(属性值)。
- void setAccessible(boolean flag):设置为true时表示打破封装,如果不调用这个方法设置为true的话就没办法获取对象的私有属性了,调用这个方法之后就可以获取到所有的属性了,包括私有属性。
4. 反射方法字节码Method(方法)
获取Method需要先获取到对应的类字节码Class,然后才能从类中获取到对应的Method(java.lang.reflect.Method)。
Method中的常用方法:
- Class> getReturnType():获取返回值的数据类型。
- Class>[] getParameterTypes():获取方法的参数类型列表。
- int getModifiers():返回修饰符列表的代号,此代号可以使用java.lang.reflect.Modifier的静态方法toString方法传入代号获取到具体的修饰符名称列表。
- Object invoke(Object obj, Object... args):调用指定对象的方法。
5. 反射构造方法Constructor(构造方法)
获取Constructor需要先获取到对应的类字节码Class,然后才能从类中获取到对应的Method(java.lang.reflect.Constructor)。
Constructor中的常用方法:
- int getModifiers():返回修饰符列表的代号,此代号可以使用java.lang.reflect.Modifier的静态方法toString方法传入代号获取到具体的修饰符名称列表。
- T newInstance(Object... initargs):使用newInstance方法创建一个实例对象。
二、注解
1. 定义注解(Annotation)
注解,或者称之为注释,也是一种引用数据类型,编译之后也会生成class文件,具体用法见示例:
[修饰符列表] @interface 注解类型名{ // 属性定义 }
注解定义示例:
// 无属性的注解定义 public @interface MyAnnotation{ // 这里面什么都不写,表示没有属性 } // 有属性的注解定义 public @interface MyAnnotation2{ // 定义一个没有默认值的属性,在使用这个注解的时候就必须给这个属性传值 // 注意,注解的属性定义是有小括号的,但它不是方法,就只是属性 String name(); // 使用default给属性指定默认值,有默认值的属性在使用时就可以不用给这个属性传值了 int id() default 2333; } // 属性只有一个,且为value时,使用时可以不用指定属性名称 public @interface MyAnnotation3{ String value(); }
注解使用示例:
// 使用:直接在类、方法、属性、形参、注解等上面使用形如”@注解类型名“的格式即可。 // 注解的使用其实就像修饰符一样在定义的前面加上就可以,但是通常的使用习惯是在定义上一行进行添加 public class AnnotationTest { public static void main(String[] args) { } // 相当于:@MyAnnotation private int id; @MyAnnotation private int id; // 注解只有一个属性,且属性名为value时,可以不用指定属性名 @MyAnnotation3("hello") public AnnotationTest() { } // 定义了没有默认值的属性的注解,就必须给这个属性传值,有默认值的属性可以传,也可以不传 @MyAnnotation2(name = "zhangsan") public static void func() { @MyAnnotation2(name = "lisi", id = 666) int i = 10; } // 相当于:@MyAnnotation public void func2(@MyAnnotation String name) @MyAnnotation public void func2(@MyAnnotation String name) { System.out.println(name); } }
注解属性类型:定义注解的属性时,属性的类型可以是byte、short、int、long、float、double、boolean、char、String、Class、枚举类型,以及这几种类型的数组形式,不能是其他的类型。有一个小技巧,属性如果是数组,并且使用时传入的数组元素只有一个的话,定义数组的大括号是可以不写的。
2. Java内置注解
内置注解在java.lang包下,常用的有:
- @Override:这个注解只能注解方法,并且只是给编译器在编译阶段做参考用的,和运行阶段的代码没有关系,编译器在编译时会检查这个方法是否是重写的父类方法,如果不是则会报错。
- @Deprecated:表示被标注的类、方法等元素已经过时了,不建议使用。在IDEA中,被标注的方法等会出现一条横线,提示你这个方法已过时。
3. 元注解
用来标注“注解类型”的注解,即注解的注解,称之为元注解,在java.lang.annotation包下。常用的元注解有:
- @Target(ANNOTATION_TYPE):用来指定被标注的注解可以出现在哪些位置上,参数为枚举类型ElementType的数组,具体有哪些枚举值可以参考帮助文档,如“@Target(ElementType.METHOD)”表示被标注的注解只能出现在方法上。
- @Retention(RUNTIME):用来指定被标注的注解最终保存在哪里,参数是一个枚举类型RetentionPolicy,有三个枚举值,“@Retention(RetentionPolicy.SOURCE)”表示被标注的注解保存在java源文件中,并不会出现在编译之后的class文件中,“@Retention(RetentionPolicy.CLASS)”表示被标注的注解保存在class文件中,“@Retention(RetentionPolicy.RUNTIME)”表示被标注的注解保存在class文件中,并且可以被反射机制读取出来。
4. 反射注解
以类的注解为例,首先获取到类的字节码对象后,使用Class对象的方法进行反射,常用的方法有:
- boolean isAnnotationPresent(MyAnnotation.class):判断一个类是否有指定的注解,这里需要传入一个指定注解的类字节码对象。
- getAnnotation(MyAnnotation.class):获取类的指定注解对象,这里需要传入一个指定注解的类字节码对象。
属性获取:通过反射拿到注解对象之后,就可以通过调用方法(其实是属性)的形式获取属性值,因为注解定义属性时,本身就自带小括号,所以看起来就是在调用方法了,如“String name = annotationObj.name();”
注:方法、属性等的注解获取也是和上面的方法一样,而且调用的方法等大多也都是一样的。
5. 注解的作用
注解通常是通过反射机制去检查被注解的类、方法等是否满足要求,比如@Override就是检查被注解的方法是否是重写父类的方法,如果不是就会编译报错。