通过反射获取方法信息
除了类,基本类型(int, double, boolean etc)和Java关键字也有其对应的类类型(Class Type):
- 获取基本类型的类类型(Class Type):
1
Class c1 = int.class;
- 获取关键字的类类型(Class Type):
1
Class c2 = void.class;
tips: Java类外的关键字(如package)没有类类型。extends, implements也没有类类型。
此外,Java还提供了获取类的方法的方法:
- 获取所有的public方法:
1
Method ms1[] = c1.getMethods();
- 获取所有自己声明的方法(不包括从父类继承来的方法):Method类提供了获取方法的参数列表的类型对应的类类型(Class Type)的方法,即如果参数类型为int,则返回int.class:
1
Method ms2[] = c1.getDeclaredMethods();
还可以获取返回类型的类类型(Class Type):1
2Method m = ms1[0];
Class paramTypes[] = m.getParameterTypes();1
Class returnType = m.getReturnType();
tips: 通过Constructor类可以获取类的构造方法,与Method类的用法一样。
如此,我们可以根据一个对象,获取它的类的所有方法信息(包括private方法, 构造方法)。代码见:github
获取成员变量构造函数信息
成员变量也有对应的类类型(Class Type),用Field表示。
- 获取所有成员变量:
1
Fields fs[] = c.getDeclaredFields();
- 获取Public的成员变量:
1
Fields fs[] = c.getFields();
- 获取成员变量的信息如此,我们可以根据一个对象,获得它的类的所有成员变量。(包括私有变量)
1
2
3
4
5
6Field field = fs[0];
Class fieldType = field.getType();
//获取成员变量的名称
String fieldName = field.getName();
//获取成员变量的类型(字符串表示)
String typeName = fieldType.getName();方法反射的基本操作
现在,假如Foo类中有个print方法:在上一部分中我们知道了如何获取一个类的所有方法。如何获取一个特定的方法呢?Class类提供了getMethod()方法来实现这个需求:1
2
3public void print(int a, int b){
System.out.print(a + b);
}这样就获取到了这个方法。同理,可以用getDeclaredMethod()来获取private的方法。1
2
3Foo foo = new Foo();
Class c = foo.getClass();
Method method = c.getMethod("print", new Class[]{int.class, int.class});
如果我们想调用foo对象的print方法,正常的思路是:另一种思路是通过print方法来“调用”foo对象。1
foo.print(1,2);
1
Object o = method.invoke(foo, 1, 2);
o的类型由返回值的类型决定,如果没有返回值则为null, 否则和返回值类型一样。注意要做强制类型转换。
这两种方法操作的结果是一样的。
方法反射的意义是什么呢?我们知道,通过方法反射我们可以获得private的方法,当我们想调用private的方法时,比如某些私有的构造方法,我们便可以通过调用方法对象的setAccessible方法,使得这些私有方法可以为我们所用。此外,方法反射在框架中的应用较为常见,由于笔者还没有研究过框架源码,所以只展开到这里,有兴趣的读者可以自行研究。
思考
我们知道,面向对象的特性之一就是封装。使用private关键字,是为了将不想暴露在外的变量或者方法对外隐藏起来。而反射却提供了获取私有变量和私有方法的能力,这不是违背了面向对象的封装思想吗?其实,当我们深入研究反射的机制后,会发现并不是如此。为了便于理解,我们暂时把这个问题放到一边,先去思考一个泛型的例子:
1 | ArrayList list1 = new ArrayList(); |
打印结果为true,大家可以自行验证。
这个结果说明,list1和list2编译之后得到的.class文件是相同的,编译之后的集合是去泛型化的。如果我们绕过编译,那么就可以向list2中添加非String对象了。思路还是用method.invoke():
1 | Method method = c2.getMethod("add", Object.class); |
通过打印list2的内容可得知,整数100现在可以加进list2中了。而这是在编译之后进行的操作,对原来的add()
方法不造成任何影响。如果你用常规的list2.add()
方法,依然不能将非String对象添加进list2中。到这里结论已经出来了,反射给我们提供了一种在运行时动态修改.class文件的能力,并不对源文件造成影响,提高了Java的灵活性。
Author: kpt
Permalink: http://kpt.ink/2019/11/22/java-reflection-2/
文章默认使用 CC BY-NC-SA 4.0 协议进行许可,使用时请注意遵守协议。