细说Java反射2值得注意的诸多细节
昨天我编写了一篇关于 Java 反射基础知识的博文,内容挺多的,涉及到了 Class 的获取,Field、Method、Constructor、Array 及 Enum 的获取与操作。如果学会了这些知识,就能阅读或者是编写大多数反射相关代码。
但是,因为反射这一块的内容实在是太多了,编写代码过程中难免会遭遇到各种各样的 Exception,一次改进的机会,一次创造的机会。反射需要耐心与细心地对待它,我们列举编写反射代码中值得注意的一些细节。
获取不存在的对象
比如获取不存在的 Class 对象,比如获取不到一个类中并不存在的 Field、Method 或者是 Constructor。 而 Field、Method 和 Constructor 都是一个 Class 对象中的成员。
获取不到 Class 对象
我们知道获取 Class 对象有 3 种方式。
通过一个对象的 getClass() 方法。
通过 .class 关键字。
通过 Class.forName()。
前两种方式,基本上是没有什么值得注意的地方,需要注意的是第 3 种,因为 Class.forName() 传递进去的参数是一个字符串类型,所以理论上你可以这样编写代码。
Class test = Class.forName("hello world");
显然,在虚拟机中并不会存在这样一个类,所以,Java 提供了一个异常用来在获取不到 Class 文件时进行抛出。这个异常是 ClassNotFoundException。我们应该对于这一块进行处理。
Class cls1 = new String("1").getClass();
Class cls2 = int.class;
try {
Class test = Class.forName("hello world");
} catch (ClassNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
System.out.println("find class error:"+e1.getMessage());
}
可以看到,只有通过 Class.forName() 的方式获取 Class 的时候才要进行异常的捕获处理,如果查找不到这个 Class 那么程序就会抛出异常。
java.lang.ClassNotFoundException: hello world
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Unknown Source)
获取不到 Field
获取不到 Field 的情况分两种:
- 确实不存在这个 Field
- 由于修饰符导致的权限问题。
获取不存在的 Field
我们先定义一个类 Base。
public class Base {
public String b;
}
我们知道,获取一个 Class 中 Field 方式有 4 种。
public Field getField(String name);
public Field getDeclaredField(String name);
public Field[] getFields();
public Field[] getDeclaredFields()
现在,我们可以用 getField() 方法获取 b 这个 Field。
Class clzBase = Base.class;
try {
Field fieldBase = clzBase.getField("b");
} catch (NoSuchFieldException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (SecurityException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
但是,我们却没有办法去获取一个不存在的 Field,如 d。
Field fielddBase = clzBase.getField("d");
它会导致程序抛出 NoSuchFieldException 异常。
java.lang.NoSuchFieldException: d
at java.lang.Class.getField(Unknown Source)
Field 存在,但获取不到
public class Base {
public String b;
int d;
}
public class NonExistTest {
public static void main(String[] args) {
Field fieldBase = clzBase.getField("d");
System.out.println(Base.class.getSimpleName()+" has a field "+ fieldBase.getName());
}
}
可以发现程序报错了。
java.lang.NoSuchFieldException: d
at java.lang.Class.getField(Unknown Source)
这是根据 API 的定义,getField() 只能获取 public 属性的 Field。这个时候改用 getDeclairedField() 方法就可以了。
Field fieldBase1 = clzBase.getDeclaredField("d");
getDeclaredField() 能够获取一个 Class 中被定义的 Field
有同学可能会想,既然 getDeclaredField() 能够获取所有修饰符类型的 Field,那么为什么还要整出一个 getField() 方法。
其实,getDeclaredField() 并非万能的,有一种属性它是没有办法获取到的,那就是从父类继承下来的 Field。
public class Base {
public String b;
int d;
}
public class Sub extends Base{
}
Class clzBase = Sub.class;
Field fieldBase1 = clzBase.getDeclaredField("b");
System.out.println(Sub.class.getSimpleName()+" has a field "+ fieldBase1.getName());
通过 getDeclaredField() 方法去获取 Sub 的父类 Base 中的 Field b,程序会抛出异常。
java.lang.NoSuchFieldException: b
at java.lang.Class.getDeclaredField(Unknown Source)
但是,通过 getField() 方法却可以获取。
Field fieldBase1 = clzBase.getField("b");
System.out.println(clzBase.getSimpleName()+" has a field "+ fieldBase1.getName());
打印结果是:
Sub has a field b
但是,getField() 也有它的局限性,因为它只能获取被 public 修饰的 Field。例如
Field fieldBase1 = clzBase.getField("d");
System.out.println(clzBase.getSimpleName()+" has a field "+ fieldBase1.getName());
上面代码中,试图用 getField() 方法去获取一个非 public 修饰的 Field,结果自然是获取不到的。
java.lang.NoSuchFieldException: d
at java.lang.Class.getField(Unknown Source)
所以,我们可以得到一张表,这张表可以说明 getField() 方法和 getDeclaredField() 方法的能力范围。
如何获取一个 Class 中继承下来的非 public 修饰的 Field
public class Base {
public String b;
protected int d;
}
public class Sub extends Base{
}
Base 是 Sub 的父类,但是通过 Sub.class 对象是没有办法获取到 Base.class 中的 Field d,因为 d 是默认的修饰符,对于这一点 getField() 只能获取 pulic 属性的 Field 如 b,getDeclaredField() 更是无能为力。
那么,问题来了。
如果非要获取一个 Class 继承下来的非 public 修饰的 Field 要怎么办?
答案是通过获取这个 Class 的 superClass。然后调用这个 superClass 的 getDeclaredField() 方法。
Class clzBase = Sub.class;
Class superClass = clzBase.getSuperclass();
Field fieldBase1 = superClass.getDeclaredField("d");
System.out.println(clzBase.getSimpleName()+" has a field "+ fieldBase1.getName());
获取不到 Method
Method、Constructor 和 Field 一样都是 Class 的成员。所以,有很多共性。因为上一小节讲过 Field 的诸多细节,所以类似的地方我会一笔带过。
获取本身就不存在的 Method
public class Base {
public String b;
protected int d;
void testDefault0(){};
public void testPublic0(){}
protected void testProtected0(){}
private void testPrivate0(){}
}
获取本身就不存在的 Method,程序会抛出一个 NoSuchMethodException 异常。
Class class1 = Base.class;
try {
Method methodtest = class1.getDeclaredMethod("hellowworld");
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Method 存在却获取不到。
同 Field 类似,这里给出一张表。
因为参数类型不匹配而找不到。
public class Base {
public String b;
protected int d;
public Base() {
super();
}
void testDefault0(){};
public void testPublic0(){}
protected void testProtected0(){}
private void testPrivate0(){}
public void test(int a,float b){}
}
现在要获取 test() 对应的 Method。
Method methodtest = class1.getDeclaredMethod("test");
如果不传入参数是获取不到的
java.lang.NoSuchMethodException: com.wwj.test.notfound.Base.test()
at java.lang.Class.getDeclaredMethod(Unknown Source)
我们应该传入对应类型的参数
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
值得注意的是,后面接受的是可变参数,也就是参数的个数不定,并且类型都是 Class。
Method methodtest = class1.getDeclaredMethod("test",int.class,float.class);
当然,这样的形式也是可以的。
Method methodtest = class1.getDeclaredMethod("test",new Class[]{int.class,float.class});
如果一个 Method,参数过多的话,推荐使用后面一种方式。
获取 Method 的时候,参数一定要一一匹配,意思是参数的数目与类型都必须对应上,不然还是会抛出 NoSuchMethodException 异常,如下面:
Method methodtest = class1.getDeclaredMethod("test",int.class,double.class);
或者
Method methodtest = class1.getDeclaredMethod("test",int.class);
获取不到 Constructor
获取本身不存在的构造器。
Class class1 = Base.class;
Constructor constructor = class1.getConstructor(int.class);
需要注意的是,它仍然会抛出 NoSuchMethodException 这个异常。并没有一个 NoSuchConstructorException 的异常,其实想想也是,构造方法本质上其实也就是一个方法,只不过在反射机制中,因为构造方法的特殊性和重要性,要以单独用 Constructor 把它与普通的 Method 区分开来了。
Constructor 存在,却获取不到。
同 Field 和 Method 一样,同样存在
getConstructor()
getDeclaredConstructor()
getConstructors()
getDeclaredConstructors()
不一样的是,getConstructor() 和 getConstructors() 也没有办法获取 SuperClass 的 Constructor。
参数不匹配的问题
这个同 Method 一样。
获取一个 Class 的内部类或者接口
public Class<?>[] getClasses()
public Class<?>[] getDeclaredClasses()
编写代码体会一下:
public class TestMember {
class enclosingClass{};
public interface testInterface{}
}
public class MemberTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
Class clz = TestMember.class;
Class[] members = clz.getDeclaredClasses();
for ( Class c : members ) {
System.out.println(c.toGenericString());
}
}
}
测试结果如下:
class com.wwj.test.member.TestMember$enclosingClass
public abstract static interface com.wwj.test.member.TestMember$testInterface
getInterfaces() 的作用
光看名字,大家可能都会觉得 getInterfaces() 的作用是获取一个类中定义的接口,但是其实不是的,getInterfaces() 获取的是一个类所有实现的接口。
public class A implements Runnable,Cloneable{
@Override
public void run() {
}
}
public class MemberTest {
public static void main(String[] args) {
Class[] interfaces = A.class.getInterfaces();
for ( Class c : interfaces ) {
System.out.println(c.toGenericString());
}
}
}
结果是:
public abstract interface java.lang.Runnable
public abstract interface java.lang.Cloneable
反射中的权限问题
操纵非 public 修饰的 Field
public class TestPermission {
private int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public TestPermission() {
super();
}
}
TestPermission 这个类有一个 int 变量 value,现在用反射的手段获取它对应的 Field 然后操纵它的值。
public class AccessTest {
public static void main(String[] args) {
TestPermission test = new TestPermission();
test.setValue(12);
System.out.println(" value is :"+test.getValue());
Class testclass = test.getClass();
try {
Field field = testclass.getDeclaredField("value");
field.set(test, 30);
System.out.println(" value is :"+test.getValue());
} catch (NoSuchFieldException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (SecurityException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
结果却报错了。
value is :12
java.lang.IllegalAccessException:
Class com.wwj.test.access.AccessTest
can not access a member of class com.wwj.test.access.TestPermission with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Unknown Source)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(Unknown Source)
at java.lang.reflect.AccessibleObject.checkAccess(Unknown Source)
at java.lang.reflect.Field.set(Unknown Source)
at com.wwj.test.access.AccessTest.main(AccessTest.java:19)
它抛出来的是一个 IllegalAccessException 异常。
修正方法也很简单。
Field field = testclass.getDeclaredField("value");
field.setAccessible(true);
可以看到结果正常了。
value is :12
value is :30
这里需要多说两句:我这里以 private 为例,其实 protected 和 default 也是一样的,但是它们不同于 private 的地方在于,它们在本 package 范围内是可见的,有兴趣的同学可以测试一下,测试代码在一个 package,而测试的类在另外一个 package。
操纵一个 final 类型的 Field
public class TestPermission {
protected int value;
public final int value1 = 0;
}
给 TestPermission 这个类新加一个字段 value1,但是它是一个被 final 修饰的属性,如果利用反射来修改它的值会怎么样呢?
TestPermission test = new TestPermission();
Class testclass = test.getClass();
try {
Field field = testclass.getDeclaredField("value1");
field.setInt(test,123);
int a = field.getInt(test);
System.out.println(" value1 is :"+a);
} catch (NoSuchFieldException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (SecurityException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
结果却遭遇了异常。
java.lang.IllegalAccessException: Can not set final int field com.wwj.test.access.TestPermission.value1 to (int)123
at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(Unknown Source)
at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(Unknown Source)
at sun.reflect.UnsafeQualifiedIntegerFieldAccessorImpl.setInt(Unknown Source)
at java.lang.reflect.Field.setInt(Unknown Source)
虽然,value1 是被 public 修饰,但是它同样被 final 修饰,这在正常的开发流程说明这个属性不能够再被改变。
如果要解决这个问题,同样可以使用 setAccessible(true) 方法。
Field field = testclass.getDeclaredField("value1");
field.setAccessible(true);
field.setInt(test,123);
int a = field.getInt(test);
System.out.println(" value1 is :"+a);
操纵非 public 修饰的 Method
public class TestPermission {
public TestPermission() {
super();
}
private void justSay() {
System.out.println("just for test meothod");
}
}
public class AccessTest {
public static void main(String[] args) {
Class clz = TestPermission.class;
try {
Method method = clz.getDeclaredMethod("justSay");
method.invoke(clz.newInstance(),null);
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
试图通过反射去操作一个 private 方法,结果报错了。
java.lang.IllegalAccessException: Class com.wwj.test.access.AccessTest can not access a member of class com.wwj.test.access.TestPermission with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Unknown Source)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(Unknown Source)
at java.lang.reflect.AccessibleObject.checkAccess(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at com.wwj.test.access.AccessTest.main(AccessTest.java:13)
它抛出来的是一个 IllegalAccessException 异常。
修正方法也很简单。
Method method = clz.getDeclaredMethod("justSay");
method.setAccessible(true);
method.invoke(clz.newInstance(),null);
操纵非 public 修饰的 Constructor
同前面两种,同样是通过 setAccessible(true) 来搞定。
所以,在反射中如果要操作被 private 修饰的对象,那么就必须调用它的 setAccessible(true)。
setAccessible() 的秘密
我们已经知道 Field、Method 和 Constructor 都有 setAccessible() 这个方法,至于是什么呢?这是因为它们有共同的祖先 AccessObject。
public class AccessibleObject implements AnnotatedElement {
public void setAccessible(boolean flag) throws SecurityException {
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(ACCESS_PERMISSION);
setAccessible0(this, flag);
}
/* Check that you aren't exposing java.lang.Class.<init> or sensitive
fields in java.lang.Class. */
private static void setAccessible0(AccessibleObject obj, boolean flag)
throws SecurityException
{
if (obj instanceof Constructor && flag == true) {
Constructor<?> c = (Constructor<?>)obj;
if (c.getDeclaringClass() == Class.class) {
throw new SecurityException("Cannot make a java.lang.Class" +
" constructor accessible");
}
}
obj.override = flag;
}
/**
* Get the value of the {@code accessible} flag for this object.
*
* @return the value of the object's {@code accessible} flag
*/
public boolean isAccessible() {
return override;
}
}
可以看到,主要是设置内部一个 override 变量。
那么,我们再以 Method 的 invoke 方法为例。
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
如果一个 Method 的 overide 为 false 的话,它就会根据 Modifiers 判断是否具有访问权限。
[Reflection.java]
public static boolean quickCheckMemberAccess(Class<?> memberClass,
int modifiers)
{
return Modifier.isPublic(getClassAccessFlags(memberClass) & modifiers);
}
这个方法主要是简单地判断 modifiers 是不是 public,如果不是的话就返回 false。所以 protected、private、default 修饰符都会返回 false,只有 public 都会返回 true。
而不是 public 修饰的话会执行下面的代码
void checkAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers)
throws IllegalAccessException
{
if (caller == clazz) { // quick check
return; // ACCESS IS OK
}
Object cache = securityCheckCache; // read volatile
Class<?> targetClass = clazz;
if (obj != null
&& Modifier.isProtected(modifiers)
&& ((targetClass = obj.getClass()) != clazz)) {
// Must match a 2-list of { caller, targetClass }.
if (cache instanceof Class[]) {
Class<?>[] cache2 = (Class<?>[]) cache;
if (cache2[1] == targetClass &&
cache2[0] == caller) {
return; // ACCESS IS OK
}
// (Test cache[1] first since range check for [1]
// subsumes range check for [0].)
}
} else if (cache == caller) {
// Non-protected case (or obj.class == this.clazz).
return; // ACCESS IS OK
}
// If no return, fall through to the slow path.
slowCheckMemberAccess(caller, clazz, obj, modifiers, targetClass);
}
// Keep all this slow stuff out of line:
void slowCheckMemberAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers,
Class<?> targetClass)
throws IllegalAccessException
{
Reflection.ensureMemberAccess(caller, clazz, obj, modifiers);
// Success: Update the cache.
Object cache = ((targetClass == clazz)
? caller
: new Class<?>[] { caller, targetClass });
// Note: The two cache elements are not volatile,
// but they are effectively final. The Java memory model
// guarantees that the initializing stores for the cache
// elements will occur before the volatile write.
securityCheckCache = cache; // write volatile
}
最终通过 Reflection 这个类的静态方法 ensureMemberAccess() 确认。
public static void ensureMemberAccess(Class<?> currentClass,
Class<?> memberClass,
Object target,
int modifiers)
throws IllegalAccessException
{
if (currentClass == null || memberClass == null) {
throw new InternalError();
}
if (!verifyMemberAccess(currentClass, memberClass, target, modifiers)) {
throw new IllegalAccessException("Class " + currentClass.getName() +
" can not access a member of class " +
memberClass.getName() +
" with modifiers \"" +
Modifier.toString(modifiers) +
"\"");
}
}
如果没有访问权限,程序将会在此抛出一个 IllegalAccessException 的异常。
所以,如果通过反射方式去操作一个 Field、Method 或者是 Constructor,最好先调用它的 setAccessible(true) 以防止程序运行异常。
Class.newInstance() 和 Constructor.newInstance() 的区别
Class.newInstance() 的使用有严格的限制,那就是一个 Class 对象中,必须存在一个无参数的 Constructor,并且这个 Constructor 必须要有访问的权限。
package com.wwj.test.newinstance;
public class TestCreate {
}
public class NewInstanceTest {
public static void main(String[] args) {
Class clz = TestCreate.class;
try {
Constructor[] constructors = clz.getDeclaredConstructors();
for ( Constructor c : constructors ) {
System.out.println(c.toString());
}
TestCreate obj = (TestCreate) clz.newInstance();
System.out.println(obj.toString());
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
如上面的代码,我们试图通过 Class.newInstance() 的方法创建一个 TestCreate 对象实例。
public com.wwj.test.newinstance.TestCreate()
com.wwj.test.newinstance.TestCreate@15db9742
Java 默认会给每个类加上一个无参的构造方法,并且它的修饰符是 public。现在,我们作一个小小的变动。
public class TestCreate {
private TestCreate() {}
}
给 TestCreate 添加相应的构造方法,但是是 private 的访问权限,我们再看测试结果。
private com.wwj.test.newinstance.TestCreate()
java.lang.IllegalAccessException:
Class com.wwj.test.newinstance.NewInstanceTest
can not access a member of class com.wwj.test.newinstance.TestCreate with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Unknown Source)
at java.lang.Class.newInstance(Unknown Source)
at com.wwj.test.newinstance.NewInstanceTest.main(NewInstanceTest.java:15)
但是,通过 Constructor.newInstance() 却没有这种限制。Constructor.newInstance() 适应任何类型的 Constructor,无论它们有参数还是无参数,只要通过 setAccessible() 控制好访问权限就可以了。
所以,一般建议优先使用 Constructor.newInstance() 去创建一个对象实例。
谨慎使用 Method.invoke() 方法
通过 Method 调用它的 invoke 方法,这应该是整个反射机制中的灵魂了。但是,正因为如此,我们就得小心处理它的很多细节。
静态方法和非静态方法的区别
public class TestMethod {
static void test1() {
System.out.println("test1");
}
void test2() {
System.out.println("test1");
}
}
public class MethodDetailTest {
public static void main(String[] args) {
TestMethod.test1();
new TestMethod().test2();
}
}
我们知道,在正常流程开发中,调用静态方法直接用 类.方法() 的形式就可以调用,而调用非静态的方法那就必须先创建对象,再通过对象调用相应的方法。
那么,对应到反射中如何正常调用静态方法和非静态方法呢?
try {
Method method1 = clz.getDeclaredMethod("test1");
method1.invoke(null);
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
Method method2 = clz.getDeclaredMethod("test2");
method2.invoke(new TestMethod());
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
关键在于 Method.invoke() 的第一个参数,static 方法因为属于类本身所以不需要对象,那么非静态方法的就必须要传入一个对象了,而且这个对象也必须是与 Method 对应的。
我们可以测试一下,看看随便给 invoke() 方法传递一个对象会如何?
Method method2 = clz.getDeclaredMethod("test2");
method2.invoke(new String("134"));
随便给它传递一个字符串,结果报错了。
java.lang.IllegalArgumentException: object is not an instance of declaring class
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at com.wwj.test.method.MethodDetailTest.main(MethodDetailTest.java:39)
抛出了一个 IllegalArgumentException 的异常,提示说 object 不是声明的 class 的对象实例。
Method.invoke() 参数的秘密
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
第一个 Object 参数代表的是对应的 Class 对象实例,这在上面一节已经见识到了。而后面的参数就是可变形参了,它接受多个参数。我们考虑一种特殊情况。
public class TestT<T>{
public void test(T t){}
}
这是一个泛型类,T 表示接受任意类型的参数。
public class MethodDetailTest {
public static void main(String[] args) {
Class clzT = TestT.class;
try {
Method tMethod = clzT.getDeclaredMethod("test",Integer.class);
tMethod.setAccessible(true);
try {
tMethod.invoke(new TestT<Integer>(),1);
} catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (NoSuchMethodException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (SecurityException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
结果却报错。
java.lang.NoSuchMethodException: com.wwj.test.method.TestT.test(java.lang.Integer)
at java.lang.Class.getDeclaredMethod(Unknown Source)
at com.wwj.test.method.MethodDetailTest.main(MethodDetailTest.java:14)
提示找不到这个方法。原因是类型擦除。
当一个方法有泛型参数时,编译器会自动向上转型,T 向上转型是 Object。所以实际上是
void test(Object t);
上面的代码试图去找 test(Integer t) 这个方法,自然是找不到。
Method tMethod = clzT.getDeclaredMethod("test",Object.class);
tMethod.setAccessible(true);
tMethod.invoke(new TestT<Integer>(),1);
这样编写代码才正常。
Method 中处理 Exception
我们平常开发中,少不了与各种异常打交道。
public class TestMethod {
public static class Super{}
public static class Sub extends Super{}
static void test1(Sub sub) {
System.out.println("test1");
}
void test2() throws IllegalArgumentException {
System.out.println("test2");
throw new IllegalArgumentException("只是用来测试异常");
}
}
为了便于测试,给 test2() 方法添加了一个异常。在 Java 反射中,一个 Method 执行时遭遇的异常会被包装在一个特定的异常中,这个异常就是 InvocationTargetException。
try {
Method method2 = clz.getDeclaredMethod("test2");
method2.invoke(clz.newInstance());
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
它会出现异常。
test2
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at com.wwj.test.method.MethodDetailTest.main(MethodDetailTest.java:63)
Caused by: java.lang.IllegalArgumentException: 只是用来测试异常
at com.wwj.test.method.TestMethod.test2(TestMethod.java:15)
... 5 more
我们如果需要在代码中手动获取被包装在 InvocationTargetException 中的异常,就要通过它的方法手动获取。
catch (InvocationTargetException e) {
// TODO Auto-generated catch block
//e.printStackTrace();
Throwable cause = e.getCause();
System.out.println(cause.toString());
}
通过调用 InvocationTargetException 对象的 getCause() 方法,会得到 Throwable 对象,原始的异常就包含在里面。打印结果如下:
java.lang.IllegalArgumentException: 只是用来测试异常
总结
反射给力的地方,在于它可以绕过一定的安全机制,比如操纵 private 修饰的 Field、Method、Constructor。并且它还有能力改变 final 属性的 Field。
但是反射头痛的地方就是它有太多的 Exception 需要处理。
总之,还是那句话:反射有风险,编码需谨慎。你能看到这里很棒!