分享

java 运行时类型识别(RTTI) - 2 - 反射

 android之情殇 2013-01-28

java 运行时类型识别(RTTI) - 1 - Class与instanceof

java 运行时类型识别(RTTI) - 2 - 反射

 

本文将叙述如何运行时查看类信息,其中包括变量,方法,以及通过反射修改变量,执行方法等

包括如何反射匿名内部类及如何执行其方法,但是笔者强烈不建议这么做,这里只是演示反射而已

 

下面是一个测试类

Java代码  收藏代码
  1. package reflect;  
  2.   
  3. public class Outer {  
  4.   
  5.     static{  
  6.         System.out.println("Testing..");  
  7.     }  
  8.       
  9.     TestInterface ti = new TestInterface() {  
  10.         public String test() {  
  11.             return "this is ti";  
  12.         }  
  13.     };  
  14.       
  15.     public Outer(String name){  
  16.         System.out.println("Outer");  
  17.     }  
  18.   
  19.     public String toString(){  
  20.         System.out.println("Outer toString");  
  21.         return new TestInterface() {  
  22.             public String test() {  
  23.                 return "this is a test!";  
  24.             }  
  25.         }.test();  
  26.     }  
  27.       
  28.     @SuppressWarnings("unused")  
  29.     private void privateMethod(){  
  30.         System.out.println("privateMethod");  
  31.     }  
  32.       
  33.     protected void protectedMethod(){  
  34.         System.out.println("protectedMethod");  
  35.     }  
  36.       
  37.     void packageMethod(){  
  38.         System.out.println("packageMethod");  
  39.     }  
  40.       
  41.     public static void staticMethod(){  
  42.         System.out.println("staticMethod");  
  43.     }  
  44.       
  45.     public interface TestInterface {  
  46.         public String test();  
  47.     }  
  48.       
  49.     public static class StaticInner {  
  50.         private static final String TAG = "StaticInnerTAG";  
  51.         public StaticInner(){  
  52.             System.out.println("StaticInner");  
  53.         }  
  54.           
  55.         public String toString(){  
  56.             System.out.println("StaticInner toString");  
  57.             return TAG;  
  58.         }  
  59.     }  
  60.       
  61.     private class Inner{  
  62.         String name;  
  63.         public Inner(){  
  64.             System.out.println("Inner");  
  65.         }  
  66.           
  67.         public Inner(String name){  
  68.             System.out.println("reflect.Outer.Inner.Inner(String name)");  
  69.             this.name = name;  
  70.         }  
  71.     }  
  72. }  
  

其中包括了普通内部类,静态内部类,内部匿名类,接口等

其中外部类Outer没有默认构造器,String reflect.Outer.Inner.getName()是private的……

 

外部类

Constructor,Method,Class.forName,newInstance

下面来看如何对上述的类进行反射

首先先看外部类Outer

Java代码  收藏代码
  1. public static void main(String[] args) {  
  2.     try {  
  3.         Class<?> outer = Class.forName("reflect.Outer");  
  4.         outer = Outer.class;  
  5.         Constructor<?>[] constructors = outer.getConstructors();  
  6.         for (Constructor<?> constructor : constructors) {  
  7.             Class<?>[] types = constructor.getParameterTypes();  
  8.             System.out.println(constructor.getName() + "(" + arrayToString(types) + ")");  
  9.         }  
  10.           
  11.         Constructor<?> outerConstructor = outer.getConstructor(String.class);  
  12.         Outer outerInstance = (Outer) outerConstructor.newInstance("a outer");  
  13.         System.out.println(outerInstance);  
  14.     } catch (ClassNotFoundException e) {  
  15.         e.printStackTrace();  
  16.     } catch (SecurityException e) {  
  17.         e.printStackTrace();  
  18.     } catch (NoSuchMethodException e) {  
  19.         e.printStackTrace();  
  20.     } catch (IllegalArgumentException e) {  
  21.         e.printStackTrace();  
  22.     } catch (InstantiationException e) {  
  23.         e.printStackTrace();  
  24.     } catch (IllegalAccessException e) {  
  25.         e.printStackTrace();  
  26.     } catch (InvocationTargetException e) {  
  27.         e.printStackTrace();  
  28.     }  
  29. }  
  30.   
  31. public static String arrayToString(Class<?>[] classes){  
  32.     StringBuilder stringBuilder = new StringBuilder();  
  33.     for (Class<?> type : classes) {  
  34.         stringBuilder.append(type.getCanonicalName());  
  35.         stringBuilder.append(",");  
  36.     }  
  37.     if(classes.length > 0){  
  38.         stringBuilder.deleteCharAt(stringBuilder.length() - 1);  
  39.     }  
  40.     return stringBuilder.toString();  
  41. }  

 

打印结果

Java代码  收藏代码
  1. Testing..  
  2. reflect.Outer(java.lang.String)  
  3. Outer  
  4. Outer toString  
  5. this is a test!  
 

 

下面简单分析下

 

Java代码  收藏代码
  1. Class<?> outer = Class.forName("reflect.Outer");  
  2. //outer = Outer.class;  

我们对类reflect.Outer进行反射,这里使用Outer.class也可以获得Class对象

之前说过使用类字面量不会初始化该类,而Class.forName则会初始化 ,当使用Outer.class时候log如下

 

Java代码  收藏代码
  1. reflect.Outer(java.lang.String)  
  2. Testing..  
  3. Outer  
  4. Outer toString  
  5. this is a test!  

 

顺序改变了,打印的时候Testing..实际是下面这句触发的

 

Java代码  收藏代码
  1. Outer outerInstance = (Outer) outerConstructor.newInstance("a outer");  

这时候加载Outer这个类,然后进行初始化

 

 

接下来我们查看了Outer的所有构造器Constructor,并打印出所需要的参数(与普通方法区别开来)

构造器只有一个,参数是String

如何执行这个带有String作为参数的构造器来返回一个Outer实例

 

Java代码  收藏代码
  1. Constructor<?> outerConstructor = outer.getConstructor(String.class);  
  2. Outer outerInstance = (Outer) outerConstructor.newInstance("a outer");  
  3. System.out.println(outerInstance);  

 

我们先取得我们需要的Constructor,然后,Constructor提供了newInstance方法,这样就可以获得Outer实例

最后打印实例,调用了toString函数

如果你尝试调用

 

Java代码  收藏代码
  1. outer.newInstance();   

 

则会看到如下异常

 

java.lang.InstantiationException: reflect.Outer

at java.lang.Class.newInstance0(Class.java:357)

at java.lang.Class.newInstance(Class.java:325)

at reflect.Test.main(Test.java:30)

 

这是因为Outer没有提供默认构造器

在获得Constructor时其实是有下面两个选择的,至于区别,我们放在Method里说

 

Java代码  收藏代码
  1. outer.getConstructors();  
  2. outer.getDeclaredConstructors()  

 

 

现在看看如何获得类的方法,顺便看看Declared的作用

不如直接打印出来,这样比较直观

 

Java代码  收藏代码
  1. Method[] methods = outer.getMethods();  
  2. for (Method method : methods) {  
  3.     Class<?>[] types = method.getParameterTypes();  
  4.     System.out.println(method.getReturnType().getName() + " " + outer.getName() + "." + method.getName() + "(" + arrayToString(types) + ")");  
  5. }  
  6. System.out.println("--------------------------------------");  
  7. methods = outer.getDeclaredMethods();  
  8. for (Method method : methods) {  
  9.     Class<?>[] types = method.getParameterTypes();  
  10.     System.out.println(method.getReturnType().getName() + " " + outer.getName() + "." + method.getName() + "(" + arrayToString(types) + ")");  
  11. }Class<?> outer = Outer.class;  

 

 

结果如下

Java代码  收藏代码
  1. void reflect.Outer.staticMethod()  
  2. java.lang.String reflect.Outer.toString()  
  3. void reflect.Outer.wait(long)  
  4. void reflect.Outer.wait()  
  5. void reflect.Outer.wait(long,int)  
  6. boolean reflect.Outer.equals(java.lang.Object)  
  7. int reflect.Outer.hashCode()  
  8. java.lang.Class reflect.Outer.getClass()  
  9. void reflect.Outer.notify()  
  10. void reflect.Outer.notifyAll()  
  11. --------------------------------------  
  12. void reflect.Outer.privateMethod()  
  13. void reflect.Outer.protectedMethod()  
  14. void reflect.Outer.packageMethod()  
  15. void reflect.Outer.staticMethod()  
  16. java.lang.String reflect.Outer.toString()  
  

调用getMethods时,Outer的方法只打印出了toString,其余结果均为父类Object的公有方法

调用getDeclaredMethods时,则只打印出在Outer中声明定义的方法

他们均打印出了公共的静态方法

所有带Declared和不带Declared的成对的方法都和上面的类似

 

如何利用反射执行一个方法,下面以void reflect.Outer.privateMethod()为例

Java代码  收藏代码
  1. Method method = outer.getDeclaredMethod("privateMethod");  
  2. method.setAccessible(true);  
  3. method.invoke(outerInstance, (Object[])null);  
  4. method.setAccessible(false);  

首先privateMethod是私有方法,所以为了能得到对应的Method对象,我们需要调用getDeclaredMethod

 

为了能访问私有方法,我们需要进行setAccessible(true)的设置

然后需要有个对象来执行,我们选择刚才的outerInstance

这时控制台输出

 

Java代码  收藏代码
  1. privateMethod  

 

干完活后,恢复其设置setAccessible(false)

 

下面是静态方法的执行

 

Java代码  收藏代码
  1. Method method = outer.getDeclaredMethod("staticMethod");  
  2. method.setAccessible(true);  
  3. method.invoke(Outer.class, (Object[])null);  
  4. method.setAccessible(false);  

 

 ps:使用method.invoke(outerInstance, (Object[])null);也能执行,但是不该这样

 

内部类

isInterface,Field,isAccessible,setAccessible,getModifiers,Modifier.toString()

下面看看内部类的反射

Java代码  收藏代码
  1. Class<?>[] classes = outer.getDeclaredClasses();  
  2. for (Class<?> clazz : classes) {  
  3.     if(clazz.isInterface()){  
  4.         System.out.println("interface : " + clazz.getName());  
  5.     }else{  
  6.         System.out.println("class : " + clazz.getName());  
  7.     }  
  8. }  

 

输出

Java代码  收藏代码
  1. class : reflect.Outer$Inner  
  2. class : reflect.Outer$StaticInner  
  3. interface : reflect.Outer$TestInterface  

 

看来反射提供的api功能还是很全的

 

下面是如何反射静态内部类

Java代码  收藏代码
  1. try {  
  2.     Class<StaticInner> staticInner = StaticInner.class;  
  3.     StaticInner staticInnerInstance = staticInner.newInstance();  
  4. }catch (SecurityException e) {  
  5.     e.printStackTrace();  
  6. catch (IllegalArgumentException e) {  
  7.     e.printStackTrace();  
  8. catch (InstantiationException e) {  
  9.     e.printStackTrace();  
  10. catch (IllegalAccessException e) {  
  11.     e.printStackTrace();  
  12. }  

 

也可以使用Class.forName方式,如果StaticInner是不可见的,那么上面的代码则无法编译,下面看看Class.forName

Java代码  收藏代码
  1. try {  
  2.     Class<StaticInner> staticInner = (Class<StaticInner>) Class.forName("reflect.Outer.StaticInner");  
  3.     StaticInner staticInnerInstance = staticInner.newInstance();  
  4. }catch (SecurityException e) {  
  5.     e.printStackTrace();  
  6. catch (IllegalArgumentException e) {  
  7.     e.printStackTrace();  
  8. catch (InstantiationException e) {  
  9.     e.printStackTrace();  
  10. catch (IllegalAccessException e) {  
  11.     e.printStackTrace();  
  12. catch (ClassNotFoundException e) {  
  13.     e.printStackTrace();  
  14. }  

 

如果你和上面的写法一样,那么很不幸,运行时会得到如下错误

java.lang.ClassNotFoundException: reflect.Outer.StaticInner

at java.net.URLClassLoader$1.run(URLClassLoader.java:217)

at java.security.AccessController.doPrivileged(Native Method)

at java.net.URLClassLoader.findClass(URLClassLoader.java:205)

at java.lang.ClassLoader.loadClass(ClassLoader.java:321)

at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:294)

at java.lang.ClassLoader.loadClass(ClassLoader.java:266)

at java.lang.Class.forName0(Native Method)

at java.lang.Class.forName(Class.java:186)

at reflect.Test.main(Test.java:11)

 

我们去bin目录下可以发现这样的文件Outer$StaticInner.class

反射代码只需做如下改动

Java代码  收藏代码
  1. Class<StaticInner> staticInner = (Class<StaticInner>) Class.forName("reflect.Outer$StaticInner");  

.变成了$

这里newInstance之所以能执行,是因为StaticInner有默认构造器

下面看看StaticInner中生命了哪些变量

Java代码  收藏代码
  1. Class<StaticInner> staticInner = StaticInner.class;  
  2. Field[] fields = staticInner.getDeclaredFields();  
  3. for (Field field : fields) {  
  4.     Class<?> type = field.getType();  
  5.     field.setAccessible(true);  
  6.     System.out.println(Modifier.toString(field.getModifiers()) + " " + type.getName() + " " + field.getName());  
  7. }  
  8.   
  9. StaticInner StaticInnerInstance = staticInner.newInstance();  
  10. Field field = staticInner.getDeclaredField("TAG");  
  11. boolean accessible = field.isAccessible();  
  12. if(!accessible){  
  13.     field.setAccessible(true);  
  14. }  
  15. System.out.println(field.get(StaticInnerInstance));  
  16. field.set(StaticInnerInstance, "TAG");  
  17. System.out.println(field.get(StaticInnerInstance));  
  18. if(!accessible){  
  19.     field.setAccessible(false);  
  20. }  

结果如下

Java代码  收藏代码
  1. private java.lang.String TAG  
  2. StaticInner  
  3. StaticInnerTAG  
  4. TAG  

StaticInner只声明了一个变量,名字为TAG,类型为String

即使是private变量,设置setAccessible(true)之后也能改变其值

普通内部类的反射和上面类似

 

最后说下匿名内部类

匿名内部类的作用是仅仅使用一次,所以也没有必要进行反射,实际上也很难进行反射

例子中有两个匿名内部类

 

Java代码  收藏代码
  1. TestInterface ti = new TestInterface() {  
  2.     public String test() {  
  3.         return "this is ti";  
  4.     }  
  5. };  
  6.   
  7. public String toString(){  
  8.     System.out.println("Outer toString");  
  9.     return new TestInterface() {  
  10.         public String test() {  
  11.             return "this is a test!";  
  12.         }  
  13.     }.test();  
  14. }  

在bin目录下发现有Outer$1.class和Outer$2.class

 

getDeclaredClasses发现也没有匿名内部类的信息

 

Java代码  收藏代码
  1. try {  
  2.     Class<Outer> outer = Outer.class;  
  3.     Class<?>[] classes = outer.getDeclaredClasses();  
  4.     for (Class<?> clazz : classes) {  
  5.         System.out.println(clazz.getName());  
  6.     }  
  7. }catch (SecurityException e) {  
  8.     e.printStackTrace();  
  9. catch (IllegalArgumentException e) {  
  10.     e.printStackTrace();  
  11. }  

 

结果

 

Java代码  收藏代码
  1. reflect.Outer$Inner  
  2. reflect.Outer$StaticInner  
  3. reflect.Outer$TestInterface  

 

只有这三个,包括一个接口

如果非要对匿名内部类进行反射,也不是不可能

下面演示如何反射一个匿名内部类,仅仅是演示,没有任何意义,也不推荐这么做

我们以上面的toString方法里面的内部类做例子

 

Java代码  收藏代码
  1. try {  
  2.     Class<?> clazz = Class.forName("reflect.Outer$2");  
  3.     Constructor<?>[] constructors = clazz.getDeclaredConstructors();  
  4.     constructors[0].setAccessible(true);  
  5.     Class<?>[] types = constructors[0].getParameterTypes();  
  6.     for (Class<?> type : types) {  
  7.         System.out.println("Parameter Types:" + type.getName());  
  8.     }  
  9.     TestInterface testInterface = (TestInterface) constructors[0].newInstance(new Outer(""));  
  10.     Method[] methods = clazz.getDeclaredMethods();  
  11.     for (Method method : methods) {  
  12.         Class<?>[] parameterTypes = method.getParameterTypes();  
  13.         System.out.println(method.getName() + "(" + arrayToString(parameterTypes) + ")");  
  14.     }  
  15.     Method method = clazz.getDeclaredMethod("test");  
  16.     String string = (String)method.invoke(testInterface);  
  17.     System.out.println(string);  
  18. }catch (SecurityException e) {  
  19.     e.printStackTrace();  
  20. catch (IllegalArgumentException e) {  
  21.     e.printStackTrace();  
  22. catch (ClassNotFoundException e) {  
  23.     e.printStackTrace();  
  24. catch (IllegalAccessException e) {  
  25.     e.printStackTrace();  
  26. catch (InvocationTargetException e) {  
  27.     e.printStackTrace();  
  28. catch (InstantiationException e) {  
  29.     e.printStackTrace();  
  30. catch (NoSuchMethodException e) {  
  31.     e.printStackTrace();  
  32. }  

 

结果如下

 

Java代码  收藏代码
  1. Parameter Types:reflect.Outer  
  2. Testing..  
  3. Outer  
  4. test()  
  5. this is a test!  

下面看看代码都做了什么,依次说明

首先获取Outer$2的Class

然后获取其构造器,查看所需要的参数,我们发现内部匿名类也需要一个外部类的支持

然后我们newInstance生成实例,声明为一个接口,由于要传入外部类,这里我们直接new一个,所以会初始化外部类,打印第2,3句

接下来查看内部类的方法,找到test(),这是第4句打印的结果

最后用实例化好的内部匿名类执行这个方法,所以打印出了最后一句

 

执行第一个匿名内部类的方法代码简单些,同样不推荐这么做

 

Java代码  收藏代码
  1. try {  
  2.     Class<Outer> clazz = Outer.class;  
  3.     Outer o = new Outer("");  
  4.     //System.out.println(o.ti.test());  
  5.     Field field = clazz.getDeclaredField("ti");  
  6.     TestInterface testInterface = (TestInterface) field.get(o);  
  7.     System.out.println(testInterface.test());  
  8. }catch (SecurityException e) {  
  9.     e.printStackTrace();  
  10. catch (IllegalArgumentException e) {  
  11.     e.printStackTrace();  
  12. catch (NoSuchFieldException e) {  
  13.     // TODO Auto-generated catch block  
  14.     e.printStackTrace();  
  15. catch (IllegalAccessException e) {  
  16.     e.printStackTrace();  
  17. }  

 

为了演示反射,这里没有使用System.out.println(o.ti.test());

 

至此,反射演示结束

由于写本篇blog的时候多次修改代码,所以上面可能有些地方不太一致,如果发现,敬请指正

 

 

转贴请保留以下链接

本人blog地址

http://su1216./

http://blog.csdn.net/su1216/

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多