Java RTTI vs 反射机制
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方法的以外部类的类型作为第一个参数。这里就不再列出代码了。
posted on 2016-07-20 18:50 alvin.zhang 阅读(116) 评论(0) 编辑 收藏 举报