类型信息
本章将讨论Java是如何让我们在运行时识别对象和类的信息的。主要有两种方式:一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型;另一种是“反射”机制,它允许我们在运行时发现和使用类的信息。
14.1为什么需要RTTI
当把Shape对象放入List<Shape>的数组时会向上转型。当在向上转型为Shape的时候也丢失了Shape对象的具体类型。对于数组而言,它们只是Shape类的对象。
当从数组中取出元素时,这种容器(实际上它将所有的事物都当作Object持有)会自动将结果转型回Shape。这是RTTI最基本的使用形式,因为在Java中,所有的类型转换都是在运行时进行正确性检查的。这也是RTTI名字的含义:在运行时,识别一个对象的类型。
使用RTTI,可以查询某个Shape引用所指向的对象的确切类型,然后选择或者剔除特例。
14.2Class对象
类型信息在运行时的表示形式是称为Class对象的特殊对象,它包含了与类有关的信息。事实上,Class对象就是用来创建类所有的“常规”对象的。Java使用Class对象来执行其RTTI,即使你在执行的是类似转型这样的操作。
类是程序的一部分,每个类都有一个Class对象。换言之,每当编写并编译了一个新类,就会产生一个Class对象(更恰当地说,是被保存在一个同名的.class文件中)。为了生成这个类的对象,运行这个程序的JVM将使用被称为“类加载器”的子系统。
所有的类都是在对其第一次使用时,动态加载到JVM中的。
class Gum{
static {print("Loading Gum");}
}
public class SweetShop {
public static void main(String[] args){
try{
Class.forName("Gum");
}catch(ClassNotFoundException e){
print("couldnot find Gum");
}
}
}
forName()方法是Class类的一个static成员。Class对象就和其它对象一样,我们可以获取操作它的引用。forName()方法是取得Class对象的引用的一种方法,返回的是一个Class对象的引用。
无论何时,只要你想在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用。方式:
Class c1 = Class.forName("Gum");
Class c2= obj.getClass();
Class c3 = Gum.class;
Gum gum = c.newInstance();
泛化的Class引用:
Class<Integer> c = int.class;
//c = double.class; //Illegal
新的转型语法,cast()方法
Building b = new Building();
Class<House> houseType = House.class;
House h = hoseType.cast(b);
h = (House) b ; //与上一行功能相同
14.3类型转换前先做检查
1.传统的类型转换,如“Shape”,由RTTI确保类型转换的正确性。
2.代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息。
3.关键字instanceof。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例。
动态的instanceof:Class.isInstance()
14.4注册工厂
使用工厂方法设计模式, 将对象的创建工作交给类自己去完成。 工厂方法可以被多态地调用, 从而为你创建恰当类型的对象。
14.5instanceof与Class的等价性
对于Base对象x:
x instanceof Base: true
x instanceof Derived:false
x.getClass() == Base.class :true
x.getClass() == Derived.class: false
对于Derived对象x
x instanceof Base: true
x instanceof Derived: true
x.getClass() == Base.class :false
x.getClass() == Derived.class: true
注:instanceof和isInstance方法生成的结果完全一样。equals和==也一样
但是这两组却不一样。instanceof保持了类型的概念,而如果用==比较对象,就没有考虑继承。
14.6反射
Class类与java.lang.reflect类库一起对反射的概念进行类支持。匿名对象的类信息能在运行时被完全确定下来,而在编译时不需要知道任何事情。
14.7动态代理
Java的动态代理比代理的思想更向前迈进了一步,因为它可以动态地创建代理并动态地处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策。
import java.lang.reflect.*;
class DynamicProxyHandler implements InvocationHandler{
private Object proxied;
public DynamicProxyHandler(Object proxied){
this.proxied = proxied;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
System.out.println("****proxy:" + proxy.getClass() + "method:" + method);
return method.invoke(proxied, args);
}
}
class SimpleDynamicProxy{
public static void consumer(Interface iface){
iface.doSomething("bonobo");
}
public static void main(String[] args){
RealObject real = new RealObject();
consume(real);
Interface proxy = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(), new Class[]{Interface.class}, new DynamicProxyHandler(real));
consume(proxy);
}
}
14.8空对象
通过引入空对象,可以假设所有对象都是有效的,而不必浪费编程精力去检查null
14.9接口与类型信息
通过使用反射,仍旧可以到达并调用所有方法,甚至是private方法!如果知道方法名,你就可以在其Method对象上调用setAccessible(true)。
14.10总结
面向对象编程语言的目的是让我们在凡是可以使用的地方都使用多态机制,只在必需的时候使用RTTI。
可继承一个新类,然后添加你需要的方法。在代码的其他地方,可以检查你自己特定的类型,并调用你自己的方法。如果在程序主体中添加需要的新特性的代码,就必须使用RTTI来检查你的特定的类型。
一致的错误报告模型的存在使我们能够通过使用反射编写动态代码。当然,尽力编写能够进行静态检查的代码是值得的,只要你确实能够这么做。但是我相信动态代码是将Java与其他诸如C++这样的语言区分开的重要工具之一。