反射不是java的专利,在多种语言中都有反射的实现,在某些解释型语言如js中,属性的调取本身就相当于一种反射。例如js中一个函数接收参数p1,而在函数内部调用p1.name即可获得p1对象的name属性,如果没有name属性则返回的undefined。然而这在强类型语言中是不能直接使用的,为了接收更广泛的类型,函数参数可能是接口或者基类,比如void f(Object obj){}而函数内部obj对象的信息无法直接获取,只能认为是个obj,就算本身是个person对象也无法调用name属性。而通过反射就能解析出运行时,对象的各种信息,比如类型,属性,方法。
反射里有三种类型需要知晓:Class Field Method,注意Class是大写的c,这和类前面的关键字class不是一个东西,这是一个类型如String.getClass()就是一个Class类型的对象,他描述了字符串类型的各种信息。Field是类中的属性或者叫字段,一个Class对象可以通过getField(“xx”)来获得名为xx的字段,Method和Field类似,不过他指的是方法。Field对象又可以通过get和set方法获取或修改,Method对象则可以通过invoke方法被调用。
我现在封装一个函数:
public void f2(Object obj)throws Exception{
//获得类
Class claz=obj.getClass();
Class parentclaz= claz.getSuperclass();
Class[] interfaces= claz.getInterfaces();
//从类中找出name属性,然后获得obj对象的name属性
Field field2=claz.getField("name");
String name = (String)field2.get(obj);
//从类中找出eat方法,然后调用obj对象的eat方法(参数为空)
Method method = claz.getMethod("eat");
method.invoke(obj);
//从类中找出eat方法,然后调用obj对象的eat方法(参数为空)
Method method2 = claz.getMethod("say",String.class);
method2.invoke(obj,"小李子");
System.out.println("类名:"+claz.getName()+";继承自:"+parentclaz+";实现了:"+ Arrays.toString(interfaces));
System.out.println("name:"+name);
}
该方法获取了当前对象的类、上一级父类、所有接口。取出name属性值。调用了无参方法eat和有参方法say。当然如果并没有name属性或者eat方法,就会抛出异常。
然后我传入一个Person对象:new Person(12,"夏娃");
一下是相关对象和接口的声明:
interface eater{void eat();}
class Person implements eater{
private int age;
public String name;
public void say(String sth){
System.out.println("hi"+sth);
}
public void eat(){
System.out.println("person eat");
}
public Person(int age,String name){
this.age=age;
this.name=name;
}
}
最终f2函数的打印结果:
person eat
hi小李子
类名:top.microfrank.Person;继承自:class java.lang.Object;实现了:[interface top.microfrank.eater]
name:夏娃
不出所料,果然类名,字段和函数都能被抽取出来。
但是。
仍有需要注意的点,上面的f2函数中将name字段的获取改为age字段,就会报错,原因是age字段是私有的,getField方法获得的是该类中能被访问的所有字段,也包括类中没有指出但是父类已经声明了的,往往javabean中字段都是私有的,这可怎么办。这时候就有了另一个(系列)方法:getDeclaredField getDeclaredMethod,和之前的方法区别就是多了一个声明这个词,这俩方法获得的是类中声明的不管是私有还是公共,只要在类中声明了就能获取。所以私有属性age可以这样获取,注意即使获取了,但是还不能操作下面代码第二行赋予了可操作权限。
Field field1=claz.getDeclaredField("age");
field1.setAccessible(true);
Integer age = (Integer)field1.get(obj);
理解getField(getMethod)和getDeclaredField(getDeclaredMethod)的区别:上面的现象中感觉像是后者范围更大,其实不然前者可以获取父类中继承下来的属性而后者执行获取当前类中声明的属性。一张图解释清楚这个作用强度:
小结:
1. 通过getClass getSuperclass getInterfaces方法可以获取对象的类型信息。
2. 通过getField getDeclaredField getMethod getDeclaredMethod可以获取类的指定属性、方法
3. 通过2中方法名对应的复数形式(如getFields)可以获取所有属性、方法。
4. 和2、3中Field Method相同用法的还有构造方法Constructor。
5. Class对象的isInstance和isInterface可以判断参数是否是个该类型的实例,newInstance方法则可以通过调用默认构造函数创建一个新的类。这也是Spring bean的创建方式。
6. 注解相关反射留到下一篇讲注解的时候讲解
Ps:注解功能及其重要,已经成为各种框架中必不可少的一部分
Pss:很多框架都使用了大量反射实现功能,有运行期间注解的必然是反射实现的,其他功能强大的应用也离不开反射,反射及其重要。