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

来源:互联网 发布:淘宝有新百伦旗舰店么 编辑:程序博客网 时间: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 种方式。

  1. 通过一个对象的 getClass() 方法。
  2. 通过 .class 关键字。
  3. 通过 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 需要处理。
这里写图片描述

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

原创粉丝点击