十四、 类型信息
运行时类型信息使得你可以在程序运行时发现和使用类型信息
如何让我们在运行时识别对象和类的信息的
1.RTTI,假定我们在编译时已经知道所有的类型
2.“反射”机制,允许我们在运行时发现和使用类的信息
14.1 为什么需要RTTI
RTTI运行时类型判定
可以用来查询某个父类引用所指向的对象的确切类型,然后选择或者剔除特例。
类型转换。
14.2 Class对象
java使用Class对象来执行其RTTI。每个类都有一个Class对象,每当编写并且编译了一个新类,就会产生一个Class对象(更恰当的说,是被保存在一个同名的.class文件中)。为生成这个类的对象,运行这个程序的jvm将使用被称为“类加载器”的子系统。
所有的类都是在对其第一次使用,动态加载到jvm中。
java程序在它开始运行之前并非被完全加载,其各个部分是在必需时才加载的。
类加载器的工作流程
1.类加载器首先检查这个类的Class对象是否已加载。若未加载,默认的类加载器就会根据类名查找.class文件。
2.在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良java代码。
3.一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。
14.2.1 类字面常量
java还提供了另一种方法生成对Class对象的引用,即类字面常量。
类字面常量不仅可以应用于普通的类,也可应用于接口,数组以及基本数据类型。对于基本数据类型的包装器类,还有标准字段TYPE,TYPE字段是个引用,指向对应的基本数据类型的Class对象
boolean.class 等价于 Boolean.TYPE
当使用“.class”来创建对Class对象的引用不会自动初始化该Class对象
使用类的过程
1.加载。类加载器执行,将查找字节码(通常在classpath所指定的路径中查找,但这并非是必需的),并从这些字节码中创建一个Class对象。
2.链接。验证类中的字节码,为静态域分配存储空间,若必需的话,将解析这个类创建的对其他类的所有引用。
3.初始化。若该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。初始化延迟到对静态方法(构造器隐式地是静态的)或者非常数静态域进行首次引用时才执行。
14.2.2 泛化的Class引用
通配符“?”,表示任何事物
Class<?> intClass = int.class;
intClass = double.class;
为创建一个Class引用,它被限定为某种类型,或该类型的任何子类型,你需要将通配符与extends关键字相结合,创建一个范围。super则表示是其自己或其超类;向Class引用添加泛型语法仅仅是为了提供编译器类型检查
Class<? extends Number> bounded = int.class;
bounded = double.class;
bounded = Number.class;
newInstance():弱类型,效率低,只能调用无参构造
new():强类型,高效率,能调用任何public构造器
public class GenericToyTest {
public static void main(String[] args) throws Exception {
Class<FancyToy> ftClass = FancyToy.class;
// Produces exact type:
FancyToy fancyToy = ftClass.newInstance();
Class<? super FancyToy> up = ftClass.getSuperclass();
// This won't compile:
//Class<Toy> up2 = ftClass.getSuperclass();//报错
// Only produces Object:
Object obj = up.newInstance();
}
} ///:~
14.2.3 新的转型语法
Class引用的转型语法,即cast()方法;cast()方法接受参数对象,并将其转型为Class引用的类型。
Class Building{}
Class House extends Building{}
main{
Building b = new Building();
Class<House> houseType = House.class;
House h = houseType.cast(b);
h = (House)b;
}
14.3 类型转换前先做检查
RTTI形式
1.传统类型转换,就抛出ClassCastException异常
2.代表对象的类型的Class对象。通过查询Class对象可获取运行时所需的信息
3.关键字instanceof。返回一个布尔值,告诉我们对象是不是某个特定类型的实例
@SuppressWarnings(“unchecked”)告诉编译器忽略 unchecked 警告信息,如使用List,ArrayList等未进行参数化产生的警告信息。
//用法:
boolean result = object instanceof class
//参数:
//result :boolean类型。
//object :必选项。任意对象表达式。
//class:必选项。任意已定义的对象类。
//(如果该x是该class的一个实例,那么返回true。如果该object 不是该class的一个实例,或者object是null,则返回false)
//一般用于向下转型
if(x instanceof Dog){
((Dog)x).bark();
}
14.3.1 使用类字面常量
14.3.2 动态的instanceof
Class.isInstanceof方法提供了一种动态地测试对象的途径。完美代替了instanceof语句
14.3.3 递归计数
14.4 注册工厂
将对象的创建工作交给类自己去完成。工厂方法可被动态调用,从而为你创建恰当类型的对象。
public class Filter extends Part{
}
public class FuelFilter extends Filter {
public static class Factory implements com.agree.ShiSi.Factory<FuelFilter>{
@Override
public FuelFilter create() {
// TODO Auto-generated method stub
return new FuelFilter();
}
}
}
public class AirFilter extends Filter {
public static class Factory implements com.agree.ShiSi.Factory<AirFilter>{
@Override
public AirFilter create() {
// TODO Auto-generated method stub
return new AirFilter();
}
}
}
public class Belt extends Part {
}
public class FanBelt extends Belt {
public static class Factory implements com.agree.ShiSi.Factory<FanBelt>{
@Override
public FanBelt create() {
// TODO Auto-generated method stub
return new FanBelt();
}
}
}
public class GeneratorBelt extends Belt{
public static class Factory implements com.agree.ShiSi.Factory<GeneratorBelt>{
@Override
public GeneratorBelt create() {
// TODO Auto-generated method stub
return new GeneratorBelt();
}
}
}
public class Part {
@Override
public String toString() {
// TODO Auto-generated method stub
return getClass().getSimpleName();
}
static List<Factory<? extends Part>> partFactories = new ArrayList<Factory<? extends Part>>();
static {
partFactories.add(new FuelFilter.Factory());
partFactories.add(new AirFilter.Factory());
partFactories.add(new FanBelt.Factory());
partFactories.add(new GeneratorBelt.Factory());
}
private static Random rand = new Random(47);
public static Part createRandom() {
int n = rand.nextInt(partFactories.size());
return partFactories.get(n).create();
}
}
public class RegisteredFactories {
public static void main(String[] args) {
for(int i=0;i<10;i++) {
System.out.println(Part.createRandom());
}
}
}
//output
FanBelt
AirFilter
FanBelt
FuelFilter
FuelFilter
FanBelt
FuelFilter
AirFilter
FanBelt
FanBelt
14.5 instanceof与Class的等价性
instanceof和isInstance()生成结果一样,equals()和==也一样
x instanceof Base
Base.class.isInstance(x)
x.getClass == Base.class
x.grtClass.equals(Base)
14.6 反射:运行时类的信息
概念:运行时获取类的信息。
快速应用开发(RAD);集成开发环境(IDE);图形化用户界面(GUI)
人们想要在运行时获取类的信息的另一个动机,便是希望提供在跨网络的远程平台上创建和运行对象的能力,称为远程方法调用(RMI)
Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field、Method以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。这样就可以使用Constructor创建新的对象,用get()和set()方法读取和修改与Filed对象管理的字段,用invoke()方法调用与Method对象关联的方法。还可以调用getFields()、getMethods()和getConstructors()等方法,以返回表示字段、方法以及构造器的对象的数组。Class.forName()生成的结果在编译时是不可知的,因此所有方法的特征签名信息都是在执行时被提取出来的。
RTTI和反射的区别在于
RTTI编译器在编译时打开和检查.class文件。反射是说.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件
14.6.1 类方法提取器
反射的作用:反射机制提供了足够的支持,使得能够创建一个在编译时完全未知的对象,并调用此对象的方法。反射在java中是用来支持其他特性的,例如对象序列化和JavaBean。
Class<?> c = Class.forName(args[0]);
Method[] methods = c.getMethods();
Constructor[] ctors = c.getConstructors();
14.7 动态代理
为了提供额外的或不同操作,而插入的用来代表“实际”对象的对象。这些操作通常涉及与“实际”对象的通信,因此通常充当中间人的角色。
只要是你想将额外的操作从“实际”对象中分离到不同的地方,特别是当你希望能够很容易地做出修改,从没有使用额外操作转为使用这些操作,或者反过来,代理就显得很有用。
interface Interface {
void doSomething();
void somethingElse(String arg);
}
class RealObject implements Interface {
public void doSomething() { print("doSomething"); }
public void somethingElse(String arg) {
print("somethingElse " + arg);
}
}
class SimpleProxy implements Interface {
private Interface proxied;
public SimpleProxy(Interface proxied) {
this.proxied = proxied;
}
public void doSomething() {
print("SimpleProxy doSomething");
proxied.doSomething();
}
public void somethingElse(String arg) {
print("SimpleProxy somethingElse " + arg);
proxied.somethingElse(arg);
}
}
class SimpleProxyDemo {
public static void consumer(Interface iface) {
iface.doSomething();
iface.somethingElse("bonobo");
}
public static void main(String[] args) {
consumer(new RealObject());
consumer(new SimpleProxy(new RealObject()));
}
} /* Output:
doSomething
somethingElse bonobo
SimpleProxy doSomething
doSomething
SimpleProxy somethingElse bonobo
somethingElse bonobo
*///:~
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 + ", args: " + args);
if(args != null)
for(Object arg : args)
System.out.println(" " + arg);
return method.invoke(proxied, args);
}
}
class SimpleDynamicProxy {
public static void consumer(Interface iface) {
iface.doSomething();
iface.somethingElse("bonobo");
}
public static void main(String[] args) {
RealObject real = new RealObject();
consumer(real);
// Insert a proxy and call again:
Interface proxy = (Interface)Proxy.newProxyInstance(
Interface.class.getClassLoader(),
new Class[]{ Interface.class },
new DynamicProxyHandler(real));
consumer(proxy);
}
} /* Output: (95% match)
doSomething
somethingElse bonobo
**** proxy: class $Proxy0, method: public abstract void Interface.doSomething(), args: null
doSomething
**** proxy: class $Proxy0, method: public abstract void Interface.somethingElse(java.lang.String), args: [Ljava.lang.Object;@42e816
bonobo
somethingElse bonobo
*///:~
通过调用了Proxy.newProxyInstance()可创建动态代理,这是这个方法所要传递的参数,动态代理可将所有调用重定向到调用处理器,通常会向调用处理器的构造器传递给一个“实际”对象的引用,从而使得调用处理器在执行其中介任务时,可请求转发。
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)
throws IllegalArgumentException
invoke()方法传递进来代理对象,以防你需要区分的请求的来源。在invoke()内部,对接口的调用将被重定向为对代理的调用。
至于它是怎么自动执行invoke()的方法,需要看产生的内部类的源码。
代理对象调接口中的方法—代理对象的真身是$proxy0 调用了对应的方法—此方法内部调用其父类的成员h调用h的invoke方法—就是调用传入了InvocationHandler的invoke方法,至于返回值,那就看我们的InvocationHandler的实现类怎么写了。
动态代理之代理工厂实现
public class ProxyFactory {
private Object obj;//目标对象
private BeforeAdvice before;
private AfterAdvice after;
public Object newProxyInstance() {
Object object = Proxy.newProxyInstance(
this.getClass().getClassLoader(), obj.getClass()
.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
if (before != null) {
before.before();
}
//result即为接口的真实实现类的返回值
Object result = method.invoke(obj, args);
if (after != null) {
after.after();
}
return result;
}
});
//object即为代理对象
return object;
}
public ProxyFactory() {
super();
// TODO Auto-generated constructor stub
}
public ProxyFactory(Object obj, BeforeAdvice before, AfterAdvice after) {
super();
this.obj = obj;
this.before = before;
this.after = after;
}
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
public BeforeAdvice getBefore() {
return before;
}
public void setBefore(BeforeAdvice before) {
this.before = before;
}
public AfterAdvice getAfter() {
return after;
}
public void setAfter(AfterAdvice after) {
this.after = after;
}
}
//前置增强
interface BeforeAdvice {
public void before();
}
//后置增强
interface AfterAdvice {
public void after();
}
14.8 空对象
空迭代器模式,它使得在组合层次结构中遍历各个节点的操作对客户端透明(客户端可使用相同的逻辑来遍历组合和叶子节点)。
需要测试一对象是否为空可创建一个空对象接口(r instanceof Null),或者用“==”和对象名.equals()
14.8.1 模拟对象与桩
空对象的逻辑变体是模拟对象和桩。
模拟对象往往是轻量级和自测试的,通常是为了处理各种不同的测试。
桩值返回桩数据,通常为重量级的,经常在测试间被复用。
14.9 接口与类型信息
public interface A {
void f();
}
public class B implements A {
public void f(){}
public void g() {}
public static void main(String[] args) {
A a = new B();
a.f();
//a.g();报错
System.out.println(a.getClass().getName());
if(a instanceof B) {
B b = (B)a;
b.g();
}
}
}
//output B