现在的位置: 首页 > 综合 > 正文

Java中的RTTI和反射机制

2019年01月15日 ⁄ 综合 ⁄ 共 5449字 ⁄ 字号 评论关闭

Java中的每一个类都对应着一个Class对象(java.lang.Class)。通过这个Class对象你可以在运行时得到很多类中的有用的信息。用Class.forName来得到一个Class对象。


  1. try {
  2.     Class c = Class.forName("MyClass");
  3.     String name = c.getName(); //
    "MyPackge.MyClass"

  4.     name = c.getSimpleName(); //
    "MyClass"

  5.     name = c.getCanonicalName(); //
    "MyPackge.MyClass"

  6.     Class superClass = c.getSuper();
  7.     if (superClass != null) //
    Object's super class is null

  8.         superClass.newInstance();
  9.     boolean b = c.isInterface();
  10.     for(java.lang.Class face: c.getInterfaces())
  11.         name = face.getName();
  12. } catch (ClassNotFoundException) {}


除了Class.forName,还可以直接用MyClass.class来得到一个Class对象。这种方式不会抛出异常。所有的类,包括基本类型,都可以使用“.class”。比如int.class。基本类型的包装类有TYPE成员,比如Integer.TYPE与int.class是一样的。

不像C++在程序启动时就把所有的静态数据与执行代码载入到内存中,java根据需要在运行时把字节码载入到内存,它分三个步骤:1、加载:类加载器查找到字节码(.class文件)并根据这些字节码创建一个Class对象;2、链接:验证类中的字节码,为静态域分配存储空间,需要的话同时解析这个类其它类的所有引用;3、初始化:当类的静态方法(构造器是特殊的静态方法)或者非常数静态域(即不是编译器常量)被首次引用时,执行静态初始化块和初始化静态数据。

Class类是支持范型的:


  1. Class<Integer> c = int.class;


Class范型的作用是可以得到编译器的支持,比如类型检查:

  1. Class<Interger> c = int.class;


除了类型检查,Class范型的new Instance会返回相应类型的对象,不仅仅是个简单的Object:

  1. Class c = int.class;
  2. Class<Integer> cc = int.class;
  3. Object o = c.newInstance();
  4. Integer i = cc.newInstance();


这种范型强制性太大,如果希望一个Class范型的引用接受别的Class类型的对象,可以使用通配符

  1. Class<?> c = int.class;
  2. = double.class;


Class<?>其实与普通的Class类是一样的,只不过显式声明你确实需要一个接受任何类型的Class对象,而不是忘了加范型参数。

如果你想让一个Class范型类既能接受int.class,又能接受double.class,但是不想接受其它非数值的类型,可以这样:


  1. Class<? extends Number> c = int.class;
  2. = double.class;
  3. Number n = c.newInstance();


?extends通配符可以让编译器确保接受的类型是某个类型的子类。另一个通配符?super可以得保证接受的类型是某个类的超类: 

  1. class Base {}
  2. class Derived extends Base {}
  3. // Class<Base> c = Derived.class.getSuperclass(); // won't compile
  4. Class<? super Derived> c = Derived.class.getSuperclass();
  5. Object o = c.newInstance();


看到上例的Class<Base>不能接受子类的getSuperClass的返回值,还是挺奇怪的,毕竟Base是Derived的基类是在编译时就确定的。不过既然编译器规定Class<? super Derived>到Class<Base>的转换,那也只能遵从了。

Class范型提供一个cast方法:

  1. Base b = new Derived;
  2. Class<Derived> c = Derived.class;
  3. Derived d = c.cast(b);


这样的cast其实与Derived d = (Derived)b;是完全等价的,所以这样的cast基本不怎么用。如果被转换的类不能被cast到目标类型的话,会抛出一个ClassCastException异常。(C++里cast是不会使用RTTI的。)另一个基本没用的方法是Class.asSubClass:

  1. Class<? extends Base> c = Derived.class.asSubclass(Base.class);
  2. Derived d = (Derived)c.newInstance();


另一个在运行时得到类型信息的方法是关键字instanceof它与Class.isInstance是等价的: 

  1. Base o = new Derived();
  2. boolean b = o instanceof Derived; //
    true

  3. = Derived.class.isInstance(o);


以上介绍的都是java的RTTI机制。Java还有一套反射机制。RTTI能够维护的类型都是编译时已知的类型,而反射可以使用一些在编译时完全不可知的类型。比如在进行一个远程调用时,类信息是通过网络传输过来的,编译器在编译时并不知道这个类的存在。下面演示如何使用反射:

  1. import java.lang.reflect.*;

  2. class SomeClass {
  3. public SomeClass() {}
  4. public int SomeMethod(double d, char c) { return 2; }
  5.     public int a;
  6. }

  7. public class ReflectTest {
  8.     public static void main(String[] args) {
  9.         Class c = Class.forName("SomeClass");
  10.         for (Constructor<?> constructor: c.getConstructors())
  11.             System.out.println(constructor);
  12.         for (Method method: c.getMethods())
  13.             System.out.println(method);
  14.         for (Field field: c.getFields())
  15.             System.out.println(field);
  16.                     
  17.         SomeClass sc = new SomeClass();
  18.         Method method = c.getMethod("SomeMethod", double.class, char.class);
  19.         Integer returnedValue = (Integer)method.invoke(sc, 3, '4');
  20.         Field field = c.getField("a");
  21.         int value = field.getInt(sc);
  22.         System.out.println(value);
  23.     }
  24. }


其实反射和RTTI并没有什么本质的区别,因为java的类都是在运行是加载并解析的,而且两者通过Class对象来获取类型信息。不同的地方就是RTTI可以直接使用方法名来调用一个方法,而不必用字符串去执行一个方法。

设计模式里有个"代理模式"。代理模式里会定义一个接口,真正工作的类和代理类都会实现这个接口,但用户只会看到代理类,而不知道真正工作的类。这个模式的好处就是可以隐藏实现细节,经常改动的地方对用户是不可见的。Java里提供了一个自动生成代理类的机制,主要使用java.lang.reflect包里的Proxy类和InvocationHandler接口:


  1. import java.lang.reflect.*;

  2. interface MyInterface {
  3.     void doSomething();
  4. }

  5. class RealWorker implements MyInterface {
  6.     public void doSomething() { System.out.println("RealWorker"); }
  7. }

  8. class MyHandler implements InvocationHandler {
  9.     public Object invoke(Object proxy, Method method, Object[] args)
  10.             throws Throwable {
  11.         return method.invoke(worker, args);
  12.     }
  13.     private MyInterface worker = new RealWorker();
  14. }

  15. public class ProxyTest {
  16.     public static void main(String[] args) {
  17.         MyInterface myProxy = (MyInterface)Proxy.newProxyInstance(
  18.                 MyInterface.class.getClassLoader(),
  19.                 new Class[] {MyInterface.class}, new MyHandler());
  20.         // call proxy's methods
  21.         myProxy.doSomething();
  22.     }
  23. }


可以看到我们只定义了一个接口和实现这个接口的类,但没有直接定义一个代理类,而是通过实现InvocationHandler和使用Proxy.newProxyInstance让编译器自动生成一个Proxy类。



通过反射机制可以做一些很违反规定的事情。你可以使用一个某个包里不对外开放的类,以及它的私有方法

  1. ///////////// HiddenClass.java ///////////////
  2. package hidden;

  3. class HiddenClass {
  4.     private void invisible() { System.out.println("invisible"); }
  5. }

  6. ///////// HiddenClassTest.java ///////////////////
  7. import java.lang.reflect.*;
  8. import hidden.*;

  9. public class HiddenClassTest {
  10.     public static void main(String[] args) {    
  11.         Class c = Class.forName("hidden.HiddenClass");
  12.             
  13.         // Object obj = c.newInstance(); // IllegalAcces***cetion
  14.         Constructor constructor = c.getDeclaredConstructor();
  15.         constructor.setAccessible(true);
  16.         Object obj = constructor.newInstance(); //
    new HiddenClass()

  17.         
  18.         Method method = c.getDeclaredMethod("invisible");
  19.         method.setAccessible(true);
  20.         method.invoke(obj); //
    call HiddenClass.invisible()

  21.     }
  22. }


当然这样的做法是很不值得提倡的。除了普通的类,同样可以用Class.forName("OuterClass$InnerClass")的方式来访问内部类。访问匿名类则用Class.forName("OuterClass$1")的方式。有一点值得注意的是,内部类和匿名类的构造函数的第一个参数是外部类的引用,所以getDeclaredConstructor方法的以外部类的类型作为第一个参数。这里就不再列出代码了。

抱歉!评论已关闭.