概述
Java反射是可以让我们在运行时获得类的方法、属性、父类、接口等类的内部信息的机制。也就是说反射本身是一个“反着来”的过程。我们通过new创建类的实例时实际上是JVM在运行时根据这个类的class对象构建出来的,而反射是在运行时通过类的class对象获得他的内部定义信息。
Class类
我们知道使用javac能够将.java文件编译成.class文件,这个.class文件包含了我们对类的原始定义信息(构造函数、方法、属性、父类、接口等)。我们又知道.class文件在运行时会被类加载器(ClassLoader)加载到JVM中,当一个.class文件被加载后,JVM会为之生成一个class对象,我们在程序中通过new实例化的对象实际上是在运行时根据相应的class对象构造出来的。确切的说这个class对象是java.lang.Class<T>泛型类的一个实例比如Class<Human>对象即为一个封装了Human类的定义信息的Class<T>实例。由于java.lang.Class<T>类不存在公有构造函数,因此我们不能直接实例化这个类,我们可以通过一下方法获得一个class对象。例如我们定义了一个Human类
class Human {
private String sex;//性别
private Integer age;//年龄
public Human(String sex,Integer age){
this.sex=sex;
this.age=age;
}
public void eat(){//吃饭行为
System.out.println("吃早餐");
}
}
1)通过类名获取其class对象
Class<Human> humanClass=Human.class;
2)通过类的完整路径名获取其class对象
try {
Class<Human> humanClass2=(Class<Human>) Class.forName("javaBasic.Human");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
3)通过对象本身获取其class对象
Human h=new Human();
Class<Human> humanClass3=(Class<Human>) h.getClass();
通过反射获取类中定义的内部信息
1) 获取类构造器
一旦我们获得了Human的class对象,我们就能够通过这个class对象获得Human类的原始定义信息,首先我们来获取Human类的构造器对象,有了构造器对象我们就能够构造出一个Human对象出来。注意,当通过反射获取到类的 Constructor、Method、Field对象后,在调用这些对象的方法之前,先将此对象的 accessible 标志设置为 true,以取消 Java 语言访问检查,可以提升反射速度。
Class<Human> humanClass=Human.class;
try {
Constructor<Human> c=humanClass.getConstructor(String.class,Integer.class);
c.setAccessible(true);
Human human=c.newInstance("男","21");
human.eat();
} catch (Exception e) {
e.printStackTrace();
}
2) 获取类中定义的方法
要获取当前类中定义的方法可以用Class中的getDeclareMethods函数,他会获得当前类中定义的所有的方法(包括public、private、static等方法),它会返回一个Method对象数组,其中每一个Method的对象即表示类中定义的一个方法。要想获得类中定义的指定的方法可以用getDeclareMethod(Stirng name,Class...<T>parameterType)。
try {
Class<Human> humanClass=Human.class;
Human human=humanClass.newInstance();//初始化对象
Method[] methods=humanClass.getDeclaredMethods();
for(Method method:methods){
System.out.println("方法名:"+method.getName());
}
//获得指定的方法
Method eat=humanClass.getDeclaredMethod("eat", String.class);
if(Modifier.isPrivate(eat.getModifiers())){//判断eat()的作用域是否为私有
System.out.println("private");
}
eat.invoke(human);//调用eat()方法
} catch (Exception e) {
e.printStackTrace();
}
3)获取类中定义的属性
获取属性和获取方法类似,只不过将getMethod()换成了getField(),getDeclaredMethod()换成了getDeclareField()。
try {
Class<Human> humanClass=Human.class;
Field[] fields=humanClass.getDeclaredFields();
for(Field field:fields){
System.out.println("属性名:"+field.getName());
}
//获得指定的属性
Field sex=humanClass.getDeclaredField("sex");
} catch (Exception e) {
e.printStackTrace();
}
4)获取父类和接口
我们先定义一个子类Man去继承Human类,如下,
class Man extends Human{
private String beard;//有胡子
@Override
public void eat(){//吃饭行为
System.out.println("只吃午餐");
}
}
然后通过class对象的getSuperClass()得到man的父类,通过getInterFaces获取接口。
Class<Man> manClass=Man.class;
Class<?> humanClass= manClass.getSuperclass();
Class<?>[] interfaces = manClass.getInterfaces();
for (Class<?> i : interfaces) {
System.out.println(i.getName());
}
总结
Java 反射是可以让我们在运行时获取类的函数、属性、父类、接口等 Class 内部信息的机制。通过反射还可以让我们在运行期实例化对象,调用方法,通过调用 get/set 方法获取变量的值,即使方法或属性是私有的的也可以通过反射的形式调用,这种“看透 class”的能力被称为内省,这种能力在框架开发中尤为重要。 有些情况下,我们要使用的类在运行时才会确定,这个时候我们不能在编译期就使用它,因此只能通过反射的形式来使用在运行时才存在的类(该类符合某种特定的规范,例如 JDBC),这是反射用得比较多的场景。
还有一个比较常见的场景就是编译时我们对于类的内部信息不可知,必须得到运行时才能获取类的具体信息。比如 ORM 框架,在运行时才能够获取类中的各个属性,然后通过反射的形式获取其属性名和值,存入数据库。这也是反射比较经典应用场景之一。