反射进阶,编写反射代码值得注意的诸多细节
来源:互联网 发布:淘宝有新百伦旗舰店么 编辑:程序博客网 时间:2024/05/20 04:26
前面一段时间,我编写了一篇关于 Java 反射基础知识的博文,内容挺多的,涉及到了 Class 的获取,Field、Method、Constructor、Array 及 Enum 的获取与操作。如果学会了这些知识,就能阅读或者是编写大多数反射相关代码。
但是,因为反射这一块的内容实在是太多了,编写代码过程中难免会遭遇到各种各样的 Exception,对于一个刚熟悉反射基础知识的新手而言,往往会感到深深的挫败感。其实,反射仍然算不难,但需要耐心与细心地对待它,本文的目的是列举编写反射代码中值得注意的一些细节,大家可以针对下面的目录部分,结合自己对反射这一块的熟悉程序,进行选择性地阅读。
- 获取不存在的对象
- 获取不到 Class 对象
- 获取不到 Field
- 获取不存在的 Field
- Field 存在但获取不到
- 如何获取一个 Class 中继承下来的非 public 修饰的 Field
- 获取不到 Method
- 获取本身就不存在的 Method
- Method 存在却获取不到
- 因为参数类型不匹配而找不到
- 获取不到 Constructor
- 获取本身不存在的构造器
- Constructor 存在却获取不到
- 参数不匹配的问题
- 获取一个 Class 的内部类或者接口
- getInterfaces 的作用
- 反射中的权限问题
- 操纵非 public 修饰的 Field
- 操纵一个 final 类型的 Field
- 操纵非 public 修饰的 Method
- 操纵非 public 修饰的 Constructor
- setAccessible 的秘密
- ClassnewInstance 和 ConstructornewInstance 的区别
- 谨慎使用 Methodinvoke 方法
- 静态方法和非静态方法的区别
- Methodinvoke 参数的秘密
- Method 中处理 Exception
- 总结
编写反射代码时,一些常见的异常。
如果你反射的基本知识都没有掌握,建议先仔细阅读我这篇文章《细说反射,Java 和 Android 开发者必须跨越的坎》
获取不存在的对象
比如获取不存在的 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 的情况分两种:
1. 确实不存在这个 Field
2. 由于修饰符导致的权限问题。
获取不存在的 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() 方法的能力范围。
另外,getFields() 和 getDeclaredField() 与 getField() 和 getDeclaredFields() 作用类似,只不过它们返回的是所有的符合条件的 Field 数组。
如何获取一个 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.frank.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()getDeclaredConstructor()
不一样的是,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.frank.test.member.TestMember$enclosingClasspublic abstract static interface com.frank.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.Runnablepublic 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(testclass, 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 :12java.lang.IllegalAccessException: Class com.frank.test.access.AccessTest can not access a member of class com.frank.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.frank.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.frank.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.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.frank.test.access.AccessTest can not access a member of class com.frank.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.frank.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() 和 Constructor.newInstance() 两种。那么,它们有什么不同的地方吗?
总体而言,Class.newInstance() 的使用有严格的限制,那就是一个 Class 对象中,必须存在一个无参数的 Constructor,并且这个 Constructor 必须要有访问的权限。
package com.frank.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.frank.test.newinstance.TestCreate()com.frank.test.newinstance.TestCreate@15db9742
Java 默认会给每个类加上一个无参的构造方法,并且它的修饰符是 public。现在,我们作一个小小的变动。
public class TestCreate { private TestCreate() {}}
给 TestCreate 添加相应的构造方法,但是是 private 的访问权限,我们再看测试结果。
private com.frank.test.newinstance.TestCreate()java.lang.IllegalAccessException: Class com.frank.test.newinstance.NewInstanceTest can not access a member of class com.frank.test.newinstance.TestCreate with modifiers "private" at sun.reflect.Reflection.ensureMemberAccess(Unknown Source) at java.lang.Class.newInstance(Unknown Source) at com.frank.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.frank.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.frank.test.method.TestT.test(java.lang.Integer) at java.lang.Class.getDeclaredMethod(Unknown Source) at com.frank.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();}
它会出现异常。
test2java.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.frank.test.method.MethodDetailTest.main(MethodDetailTest.java:63)Caused by: java.lang.IllegalArgumentException: 只是用来测试异常 at com.frank.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 需要处理。
总之,还是那句话:反射有风险,编码需谨慎。
- 反射进阶,编写反射代码值得注意的诸多细节
- 反射进阶,编写反射代码值得注意的诸多细节
- 值得注意的小细节
- 值得注意的C代码编写习惯与风格
- java编写代码需要注意的小细节
- Paint的一些值得注意的细节
- js值得注意的一个细节
- 反射的代码
- java反射编写泛型数组代码
- 使用反射编写泛型数组代码
- 第三十二章 反射的更多细节
- javaScript编写时候应该注意的细节
- java进阶之java的反射机制
- 【Java进阶】Java反射的使用
- 反射代码!!!!!!
- 编写代码的一些小细节,主要是性能方面的注意吧
- cocosbuilder 游戏动画开发利器-值得注意的几个细节
- 【JDBC开发】JDBC一些值得注意的细节问题
- apk资源文件编译报”MissingTranslation“错误
- 2017.06.27工作日记
- php 闭包, 匿名函数
- LCA --- 常规的三种算法
- webservice概念
- 反射进阶,编写反射代码值得注意的诸多细节
- Hadoop认识
- 第四章、flume源码解析之核心类(source)
- 剑指offer算法题之循环链表--约瑟夫问题,面试题45:圆圈中最后剩下的数字(补充:define和typedef)
- HDU 6053 TrickGCD (桶装+分段 / 莫比乌斯反演)
- 自定义函数名称不能与系统函数名称重复
- Java之导出可执行JAR包 Select a 'Java Application' launch configuration to use to create a runnable JAR.
- 关于Class File Editor Source not found
- Android-SurfaceView示例