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 compileClass<? 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; // trueb = 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