反射进阶,编写反射代码值得注意的诸多细节

xiaoxiao2021-02-28  110

但是,因为反射这一块的内容实在是太多了,编写代码过程中难免会遭遇到各种各样的 Exception,对于一个刚熟悉反射基础知识的新手而言,往往会感到深深的挫败感。其实,反射仍然算不难,但需要耐心与细心地对待它,本文的目的是列举编写反射代码中值得注意的一些细节,大家可以针对下面的目录部分,结合自己对反射这一块的熟悉程序,进行选择性地阅读。

获取不存在的对象 获取不到 Class 对象获取不到 Field 获取不存在的 FieldField 存在但获取不到如何获取一个 Class 中继承下来的非 public 修饰的 Field 获取不到 Method 获取本身就不存在的 MethodMethod 存在却获取不到因为参数类型不匹配而找不到 获取不到 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"); 1 1

显然,在虚拟机中并不会存在这样一个类,所以,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()); } 12345678910111213 12345678910111213

可以看到,只有通过 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) 12345678910 12345678910

获取不到 Field

获取不到 Field 的情况分两种:  1. 确实不存在这个 Field  2. 由于修饰符导致的权限问题。

获取不存在的 Field

我们先定义一个类 Base。

public class Base { public String b; } 1234 1234

我们知道,获取一个 Class 中 Field 方式有 4 种。

public Field getField(String name); public Field getDeclaredField(String name); public Field[] getFields(); public Field[] getDeclaredFields() 12345678 12345678

现在,我们可以用 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(); } 12345678910 12345678910

但是,我们却没有办法去获取一个不存在的 Field,如 d。

Field fielddBase = clzBase.getField("d"); 1 1

它会导致程序抛出 NoSuchFieldException 异常。

java.lang.NoSuchFieldException: d at java.lang.Class.getField(Unknown Source) 12 12

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()); } } 12345678910111213141516 12345678910111213141516

可以发现程序报错了。

java.lang.NoSuchFieldException: d at java.lang.Class.getField(Unknown Source) 12 12

这是根据 API 的定义,getField() 只能获取 public 属性的 Field。这个时候改用 getDeclairedField() 方法就可以了。

Field fieldBase1 = clzBase.getDeclaredField("d"); 1 1

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()); 1234567891011121314 1234567891011121314

通过 getDeclaredField() 方法去获取 Sub 的父类 Base 中的 Field b,程序会抛出异常。

java.lang.NoSuchFieldException: b at java.lang.Class.getDeclaredField(Unknown Source) 12 12

但是,通过 getField() 方法却可以获取。

Field fieldBase1 = clzBase.getField("b"); System.out.println(clzBase.getSimpleName()+" has a field "+ fieldBase1.getName()); 123 123

打印结果是:

Sub has a field b 1 1

但是,getField() 也有它的局限性,因为它只能获取被 public 修饰的 Field。例如

Field fieldBase1 = clzBase.getField("d"); System.out.println(clzBase.getSimpleName()+" has a field "+ fieldBase1.getName()); 1234 1234

上面代码中,试图用 getField() 方法去获取一个非 public 修饰的 Field,结果自然是获取不到的。

java.lang.NoSuchFieldException: d at java.lang.Class.getField(Unknown Source) 12 12

所以,我们可以得到一张表,这张表可以说明 getField() 方法和 getDeclaredField() 方法的能力范围。 

另外,getFields() 和 getDeclaredField() 与 getField() 和 getDeclaredFields() 作用类似,只不过它们返回的是所有的符合条件的 Field 数组。

如何获取一个 Class 中继承下来的非 public 修饰的 Field

先观察这两个类。

public class Base { public String b; protected int d; } public class Sub extends Base{ } 123456789 123456789

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()); 12345 12345

获取不到 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(){} } 1234567891011121314 1234567891011121314

获取本身就不存在的 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(); } 123456789101112 123456789101112

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){} } 123456789101112131415161718192021 123456789101112131415161718192021

现在要获取 test() 对应的 Method。

Method methodtest = class1.getDeclaredMethod("test"); 12 12

如果不传入参数是获取不到的

java.lang.NoSuchMethodException: com.frank.test.notfound.Base.test() at java.lang.Class.getDeclaredMethod(Unknown Source) 12 12

我们应该传入对应类型的参数

public Method getDeclaredMethod(String name, Class<?>... parameterTypes) 1 1

值得注意的是,后面接受的是可变参数,也就是参数的个数不定,并且类型都是 Class。

Method methodtest = class1.getDeclaredMethod("test",int.class,float.class); 1 1

当然,这样的形式也是可以的。

Method methodtest = class1.getDeclaredMethod("test",new Class[]{int.class,float.class}); 1 1

如果一个 Method,参数过多的话,推荐使用后面一种方式。

获取 Method 的时候,参数一定要一一匹配,意思是参数的数目与类型都必须对应上,不然还是会抛出 NoSuchMethodException 异常,如下面:

Method methodtest = class1.getDeclaredMethod("test",int.class,double.class); 或者 Method methodtest = class1.getDeclaredMethod("test",int.class); 123456 123456

获取不到 Constructor

获取本身不存在的构造器。

Class class1 = Base.class; Constructor constructor = class1.getConstructor(int.class); 1234 1234

需要注意的是,它仍然会抛出 NoSuchMethodException 这个异常。并没有一个 NoSuchConstructorException 的异常,其实想想也是,构造方法本质上其实也就是一个方法,只不过在反射机制中,因为构造方法的特殊性和重要性,要以单独用 Constructor 把它与普通的 Method 区分开来了。

Constructor 存在,却获取不到。

同 Field 和 Method 一样,同样存在

getConstructor() getDeclaredConstructor() getConstructors() getDeclaredConstructor() 12345678 12345678

不一样的是,getConstructor() 和 getConstructors() 也没有办法获取 SuperClass 的 Constructor。

参数不匹配的问题

这个同 Method 一样。

获取一个 Class 的内部类或者接口

public Class<?>[] getClasses() public Class<?>[] getDeclaredClasses() 1234 1234

编写代码体会一下:

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()); } } } 123456789101112131415161718192021222324 123456789101112131415161718192021222324

测试结果如下:

class com.frank.test.member.TestMember$enclosingClass public abstract static interface com.frank.test.member.TestMember$testInterface 123 123

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()); } } } 12345678910111213141516171819202122 12345678910111213141516171819202122

结果是:

public abstract interface java.lang.Runnable public abstract interface java.lang.Cloneable 123 123

反射中的权限问题

操纵非 public 修饰的 Field

public class TestPermission { private int value; public int getValue() { return value; } public void setValue(int value) { this.value = value; } public TestPermission() { super(); } } 123456789101112131415161718192021 123456789101112131415161718192021

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(); } } 123456789101112131415161718192021222324252627282930 123456789101112131415161718192021222324252627282930

结果却报错了。

value is :12 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.Field.set(Unknown Source) at com.frank.test.access.AccessTest.main(AccessTest.java:19) 12345678910 12345678910

它抛出来的是一个 IllegalAccessException 异常。  修正方法也很简单。

Field field = testclass.getDeclaredField("value"); field.setAccessible(true); 123 123

可以看到结果正常了。

value is :12 value is :30 12 12

这里需要多说两句:我这里以 private 为例,其实 protected 和 default 也是一样的,但是它们不同于 private 的地方在于,它们在本 package 范围内是可见的,有兴趣的同学可以测试一下,测试代码在一个 package,而测试的类在另外一个 package。

操纵一个 final 类型的 Field

public class TestPermission { protected int value; public final int value1 = 0; } 1234567 1234567

给 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(); } 12345678910111213141516171819202122232425 12345678910111213141516171819202122232425

结果却遭遇了异常。

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) 123456 123456

虽然,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); 1234567 1234567

操纵非 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(); } } } 123456789101112131415161718192021222324252627282930313233343536373839404142434445 123456789101112131415161718192021222324252627282930313233343536373839404142434445

试图通过反射去操作一个 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) 1234567 1234567

它抛出来的是一个 IllegalAccessException 异常。  修正方法也很简单。

Method method = clz.getDeclaredMethod("justSay"); method.setAccessible(true); method.invoke(clz.newInstance(),null); 1234 1234

操纵非 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; } } 1234567891011121314151617181920212223242526272829303132 1234567891011121314151617181920212223242526272829303132

可以看到,主要是设置内部一个 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); } 12345678910111213141516 12345678910111213141516

如果一个 Method 的 overide 为 false 的话,它就会根据 Modifiers 判断是否具有访问权限。  [Reflection.java]

public static boolean quickCheckMemberAccess(Class<?> memberClass, int modifiers) { return Modifier.isPublic(getClassAccessFlags(memberClass) & modifiers); } 123456 123456

这个方法主要是简单地判断 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 } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849

最终通过 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) + "\""); } } 123456789101112131415161718192021 123456789101112131415161718192021

如果没有访问权限,程序将会在此抛出一个 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(); } } } 1234567891011121314151617181920212223242526272829 1234567891011121314151617181920212223242526272829

如上面的代码,我们试图通过 Class.newInstance() 的方法创建一个 TestCreate 对象实例。

public com.frank.test.newinstance.TestCreate() com.frank.test.newinstance.TestCreate@15db9742 123 123

Java 默认会给每个类加上一个无参的构造方法,并且它的修饰符是 public。现在,我们作一个小小的变动。

public class TestCreate { private TestCreate() {} } 1234 1234

给 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) 12345678 12345678

但是,通过 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(); } } 123456789101112131415161718192021 123456789101112131415161718192021

我们知道,在正常流程开发中,调用静态方法直接用 类.方法() 的形式就可以调用,而调用非静态的方法那就必须先创建对象,再通过对象调用相应的方法。

那么,对应到反射中如何正常调用静态方法和非静态方法呢?

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(); } 123456789101112131415161718192021222324252627282930313233343536373839404142 123456789101112131415161718192021222324252627282930313233343536373839404142

关键在于 Method.invoke() 的第一个参数,static 方法因为属于类本身所以不需要对象,那么非静态方法的就必须要传入一个对象了,而且这个对象也必须是与 Method 对应的。

我们可以测试一下,看看随便给 invoke() 方法传递一个对象会如何?

Method method2 = clz.getDeclaredMethod("test2"); method2.invoke(new String("134")); 1234 1234

随便给它传递一个字符串,结果报错了。

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) 123456 123456

抛出了一个 IllegalArgumentException 的异常,提示说 object 不是声明的 class 的对象实例。

Method.invoke() 参数的秘密

public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException 123 123

第一个 Object 参数代表的是对应的 Class 对象实例,这在上面一节已经见识到了。而后面的参数就是可变形参了,它接受多个参数。我们考虑一种特殊情况。

public class TestT<T>{ public void test(T t){} } 123456 123456

这是一个泛型类,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(); } } } 12345678910111213141516171819202122232425 12345678910111213141516171819202122232425

结果却报错。

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) 1234 1234

提示找不到这个方法。原因是类型擦除

当一个方法有泛型参数时,编译器会自动向上转型,T 向上转型是 Object。所以实际上是

void test(Object t); 1 1

上面的代码试图去找 test(Integer t) 这个方法,自然是找不到。

Method tMethod = clzT.getDeclaredMethod("test",Object.class); tMethod.setAccessible(true); tMethod.invoke(new TestT<Integer>(),1); 12345 12345

这样编写代码才正常。

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("只是用来测试异常"); } } 12345678910111213141516 12345678910111213141516

为了便于测试,给 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(); } 123456789101112131415161718192021222324 123456789101112131415161718192021222324

它会出现异常。

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.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 1234567891011 1234567891011

我们如果需要在代码中手动获取被包装在 InvocationTargetException 中的异常,就要通过它的方法手动获取。

catch (InvocationTargetException e) { // TODO Auto-generated catch block //e.printStackTrace(); Throwable cause = e.getCause(); System.out.println(cause.toString()); } 12345678 12345678

通过调用 InvocationTargetException 对象的 getCause() 方法,会得到 Throwable 对象,原始的异常就包含在里面。打印结果如下:

java.lang.IllegalArgumentException: 只是用来测试异常 12 12

总结

反射牛逼的地方,在于它可以绕过一定的安全机制,比如操纵 private 修饰的 Field、Method、Constructor。并且它还有能力改变 final 属性的 Field。

但是反射头痛的地方就是它有太多的 Exception 需要处理。 

总之,还是那句话:反射有风险,编码需谨慎。

转载请注明原文地址: https://www.6miu.com/read-68673.html

最新回复(0)