1. 反射基础
1.1 什么是反射?
Java反射是可以让我们在运行时获取类的方法、属性、父类、接口等 Class 内部信息的机制。通过反射还可以让我们在运行期实例化对象,调用方法,通过调用 get/set 方法获取变量的值,即使方法或属性是私有的的也可以通过反射的形式调用,这种“看透 class”的能力被称为内省,这种能力在框架开发中尤为重要。 有些情况下,我们要使用的类在运行时才会确定,这个时候我们不能在编译期就使用它,因此只能通过反射的形式来使用在运行时才存在的类(该类符合某种特定的规范,例如 JDBC),这是反射用得比较多的场景。
还有一个比较常见的场景就是编译时我们对于类的内部信息不可知,必须得到运行时才能获取类的具体信息。比如 ORM 框架,在运行时才能够获取类中的各个属性,然后通过反射的形式获取其属性名和值,存入数据库。这也是反射比较经典应用场景之一
1.2 Class类
反射是操作Class信息的
当我们编写完一个 Java 项目之后,所有的 Java 文件都会被编译成一个.class 文件,这些 Class 对象承载了这个类型的父类、接口、构造方法、成员方法、成员字段等原始信息,这些 class 文件在程序运行时会被 ClassLoader 加载到虚拟机中。当一个类被加载以后,Java 虚拟机就会在内存中自动产生一个 Class 对象。我们通过 new 的形式创建对象实际上就是通过这些 Class 来创建,只是这个过程对于我们是不透明的而已。
反射举例说明的相关类
// 父类
public class Animal {
private String name;
public Animal() {}
public Animal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private void animalOwnMethod() {
System.out.println("animal own method");
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
'}';
}
}
// 接口
public interface Sleep {}
// 子类
public class Cat extends Animal implements Sleep {
private String color;
public int age;
public Cat() {
super();
}
public Cat(String name) {
super(name);
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
private void catOwnMethod() {
System.out.println("cat own method");
}
@Override
public String toString() {
return "Cat{" +
"name='" + getName() + '\'' +
", color='" + color + '\'' +
", age='" + age + '\'' +
'}';
}
}
2. 反射Class以及构造对象
2.1 获取Class对象
- 通过类名来获取Class对象:类名.class
Class<?> cls = Cat.class;
- 通过对象名来获取Class对象:对象名.getClass()
Cat cat = new Cat();
Class<?> cls = cat.getClass()
- 通过Class.forName()来获取Class对象
Class<?> cls = Class.forName("com.reflection.Cat");
- 在使用 Class.forName()方法时,你必须提供一个类的全名(包含包名)
- 如果在调用 Class.forName()方法时,没有在编译路径下(classpath)找到对应的类,那么将会抛出 ClassNotFoundException。
2.2 通过 Class 对象构造目标类型的对象
我们首先要获取类的 Constructor(构造器)对象,然后通过 Constructor 来创建目标类的对象。
try {
Class<?> cls = Class.forName("com.reflection.Cat");
// 无参构造方法
// Constructor<?> constructor = cls.getConstructor();
// Object obj = constructor.newInstance();
// 有参构造方法
Constructor<?> constructor = cls.getConstructor(String.class);
Object obj = constructor.newInstance("cat");
System.out.println(obj.toString());
} catch (Exception e) {
e.printStackTrace();
}
方法说明
// 获取Constructor对象,参数为可变参数
// 如果构造函数有参数,那么需要将参数的类型(Class)传递进去
public Constructor<T> getConstructor(Class<?>... parameterTypes)
// 创建一个实例对象,参数为可变参数
// 这里的可变参数必须要与上面方法(getConstructor)的参数一一对应(包括参数数量和类型)。
// 否则会抛出异常:java.lang.IllegalArgumentException: argument type mismatch
public T newInstance(Object ... initargs)
3. 反射获取类中的方法
3.1 获取当前类中定义的方法
try {
Class<?> cls = Class.forName("com.reflection.Cat");
// 获取当前类定义的所有方法:包括public/default/protected/private
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
Constructor<?> constructor = cls.getConstructor(String.class);
Object obj = constructor.newInstance("cat");
// 获取某个指定方法的Method对象
Method method = cls.getDeclaredMethod("catOwnMethod");
// 取消对private的访问限制
method.setAccessible(true);
// 方法调用
method.invoke(obj);
} catch (Exception e) {
e.printStackTrace();
}
注意:当通过反射获取由private修饰的Constructor、Method、Field时,必须在反射调用之前将此对象的 accessible 标志设置为 true,以此来取消访问限制。
值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。
3.2 获取当前类、父类中定义的公有方法
try {
Class<?> cls = Class.forName("com.reflection.Cat");
// 获取当前类、父类中定义的所有公有方法(public)
Method[] methods = cls.getMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
Constructor<?> constructor = cls.getConstructor();
Object obj = constructor.newInstance();
// 获取当前类、父类中定义的指定公有方法
Method setScoreMethod = cls.getMethod("setColor", String.class);
setScoreMethod.invoke(obj, "white");
System.out.println(obj.toString());
} catch (Exception e) {
e.printStackTrace();
}
方法说明
// 获取当前 Class 对象中的所有方法,不包括父类的方法
public Method[] getDeclaredMethods()
// 获取 Class 对象中指定函数名和参数的函数,
// 参数1为函数名,参数2为参数类型列表
public Method getDeclaredMethod(String name, Class...<?> parameterTypes)
// 获取该 Class 对象中的所有公有方法,包括父类的公有方法
public Method[] getMethods()
// 获取指定的 Class 对象中的公有方法,
// 参数1为函数名,参数2为参数类型列表
public Method getMethod (String name, Class...<?> parameterTypes)
// 方法调用 Method#invoke
// 参数1为该Class的实例对象,参数2为要方法传递的参数列表,必须要与getMethod()或getDeclaredMethod()方法的参数列表一一对应
public native Object invoke(Object obj, Object... args)
4. 反射获取类中的字段
其实与反射获取类中的方法类似,只是调用方法不同
4.1 获取当前类中定义的字段
try {
Class<?> cls = Class.forName("com.reflection.Cat");
// 获取当前类中定义的所有字段
Field[] fields = cls.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName());
}
Constructor<?> constructor = cls.getConstructor();
Object obj = constructor.newInstance();
// 获取当前类中定义的指定字段
Field scoreField = cls.getDeclaredField("color");
scoreField.setAccessible(true);
scoreField.set(obj, "white");
System.out.println(obj.toString());
} catch (Exception e) {
e.printStackTrace();
}
4.2 获取当前类、父类中定义的公有字段
try {
Class<?> cls = Class.forName("com.reflection.Cat");
// 获取当前类、父类中定义的所有公有字段
Field[] fields = cls.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
Constructor<?> constructor = cls.getConstructor();
Object obj = constructor.newInstance();
Field scoreField = cls.getField("age");
scoreField.set(obj, 3);
System.out.println(obj.toString());
} catch (Exception e) {
e.printStackTrace();
}
5. 反射获取父类与接口
5.1 获取父类
try {
Class<?> cls = Class.forName("com.reflection.Cat");
Class<?> superCls = cls.getSuperclass();
while (superCls != null) {
System.out.println(superCls.getName());
superCls = superCls.getSuperclass();
}
} catch (Exception e) {
e.printStackTrace();
}
5.2 获取接口
try {
Class<?> cls = Class.forName("com.reflection.Cat");
Class<?>[] clsArr = cls.getInterfaces();
for (Class<?> clazz : clsArr) {
System.out.println(clazz.getName());
}
} catch (Exception e) {
e.printStackTrace();
}
6. 获取注解信息
注解与反射的组合是在框架开发中,是最为常见形式的。如常见的Retrofit、ORM框架等都使用了注解。但是这些框架处理注解形式有些差异:有些使用反射来处理注解,有些使用APT来处理注解。我们这里只讨论反射。
Java注解详情请参考:Java基础之注解Annotation总结
定义一个注解
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnoTest {
String tag();
}
使用注解
@AnnoTest(tag = "cat class")
public class Cat extends Animal implements Sleep {
@AnnoTest(tag = "color field")
private String color;
...
}
获取注解信息
Class<?> cls = Class.forName("com.reflection.Cat");
// 获取类上的注解信息
AnnoTest classAnno = cls.getAnnotation(AnnoTest.class);
System.out.println(classAnno.tag());
Field field = cls.getDeclaredField("color");
// 获取字段上的注解信息
AnnoTest fieldAnno = field.getAnnotation(AnnoTest.class);
System.out.println(fieldAnno.tag());
注:反射要获取注解信息,注解必须要声明为运行期注解,即
RetentionPolicy.RUNTIME