Java RTTI vs 反射机制

java

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


  • try {
  •     Class c = Class.forName("MyClass");
  •     String name = c.getName(); // "MyPackge.MyClass"
  •     name = c.getSimpleName(); // "MyClass"
  •     name = c.getCanonicalName(); // "MyPackge.MyClass"
  •     Class superClass = c.getSuper();
  •     if (superClass != null) // Object's super class is null
  •         superClass.newInstance();
  •     boolean b = c.isInterface();
  •     for(java.lang.Class face: c.getInterfaces())
  •         name = face.getName();
  • } catch (ClassNotFoundException) {}

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

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

    Class类是支持范型的:


  • Class<Integer> c = int.class;

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

  • Class<Interger> c = int.class;

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

  • Class c = int.class;
  • Class<Integer> cc = int.class;
  • Object o = c.newInstance();
  • Integer i = cc.newInstance();

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

  • Class<?> c = int.class;
  • c = double.class;

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

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


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

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

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

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

    Class范型提供一个cast方法:

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

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

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

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

  • Base o = new Derived();
  • boolean b = o instanceof Derived; // true
  • b = Derived.class.isInstance(o);

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

  • import java.lang.reflect.*;
  • class SomeClass {
  • public SomeClass() {}
  • public int SomeMethod(double d, char c) { return 2; }
  •     public int a;
  • }
  • public class ReflectTest {
  •     public static void main(String[] args) {
  •         Class c = Class.forName("SomeClass");
  •         for (Constructor<?> constructor: c.getConstructors())
  •             System.out.println(constructor);
  •         for (Method method: c.getMethods())
  •             System.out.println(method);
  •         for (Field field: c.getFields())
  •             System.out.println(field);
  •                     
  •         SomeClass sc = new SomeClass();
  •         Method method = c.getMethod("SomeMethod", double.class, char.class);
  •         Integer returnedValue = (Integer)method.invoke(sc, 3, '4');
  •         Field field = c.getField("a");
  •         int value = field.getInt(sc);
  •         System.out.println(value);
  •     }
  • }

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

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


  • import java.lang.reflect.*;
  • interface MyInterface {
  •     void doSomething();
  • }
  • class RealWorker implements MyInterface {
  •     public void doSomething() { System.out.println("RealWorker"); }
  • }
  • class MyHandler implements InvocationHandler {
  •     public Object invoke(Object proxy, Method method, Object[] args)
  •             throws Throwable {
  •         return method.invoke(worker, args);
  •     }
  •     private MyInterface worker = new RealWorker();
  • }
  • public class ProxyTest {
  •     public static void main(String[] args) {
  •         MyInterface myProxy = (MyInterface)Proxy.newProxyInstance(
  •                 MyInterface.class.getClassLoader(),
  •                 new Class[] {MyInterface.class}, new MyHandler());
  •         // call proxy's methods
  •         myProxy.doSomething();
  •     }
  • }

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


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

  • ///////////// HiddenClass.java ///////////////
  • package hidden;
  • class HiddenClass {
  •     private void invisible() { System.out.println("invisible"); }
  • }
  • ///////// HiddenClassTest.java ///////////////////
  • import java.lang.reflect.*;
  • import hidden.*;
  • public class HiddenClassTest {
  •     public static void main(String[] args) {    
  •         Class c = Class.forName("hidden.HiddenClass");
  •             
  •         // Object obj = c.newInstance(); // IllegalAcces***cetion
  •         Constructor constructor = c.getDeclaredConstructor();
  •         constructor.setAccessible(true);
  •         Object obj = constructor.newInstance(); // new HiddenClass()
  •         
  •         Method method = c.getDeclaredMethod("invisible");
  •         method.setAccessible(true);
  •         method.invoke(obj); // call HiddenClass.invisible()
  •     }
  • }

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

     

     

     

     

    以上是 Java RTTI vs 反射机制 的全部内容, 来源链接: utcz.com/z/394378.html

    回到顶部