第十四章 类型信息
关键词:
RTTI,加载,泛型class引用,instanceof,isInatance,工厂设计模式,反射机制,代理设计模式,动态代理,空对象,反射获取private对象
运行时类型信息可以使得在程序运行时发现和使用类型信息。
Java在运行时识别对象和类信息的两种方式:
1、RTTI,假定我们在编译时就知道了所有类型
2、“反射”机制,允许在运行时发现和使用类信息
1、为什么需要RTTI
RTTI:
Run-Time Type Identification,运行时对象类型识别。
在Java中,所有的类型转换都是在
运行时进行正确性检查的。
2、Class对象
Java使用Class对象来执行其RTTI。
每当编写且编译了一个新类,就会产生一个Class对象(被保存在.class文件中)。为了生成这个对象,运行这个程序的Java虚拟机将使用被称为“类加载器”的子系统。
类加载器子系统可以包含一条类加载器链,但是只有一个原生类加载器。原生加载器通常从本地盘加载,加载的都是可信类,包括Java API类。
所有的类都是对其第一次调用时,动态加载到JVM,当程序创建第一个对类的静态成员的引用时,就会加载整个类。因此证明类的构造器也是类的静态方法。
类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认类加载器就会根据类名查找.class文件。在这个类的字节码(.class文件)被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良代码。
Class类的一些方法:
forName("类名")返回一个Class对象的引用,主要被用作如果类没有被加载就加载它
getName()返回全限定的类名
getSimpleName()返回不含包名的类名
getCanonicalName() 返回全限定类名
getInterfaces()返回Class对象的接口们
getSuperclass()返回直接基类
newInstance()得到Object引用(实现“虚拟构造器”的一种途径,它允许你声明:“我不知道你的确切类型,但是无论如何都要争取”,newInstance()创建的类必须有默认构造器)
类字面常量
使用类字面常量可以生成对Class对象的引用。
比如酱紫:
Shapes.class
对于类字面常量,在编译时就会检查,更高效安全。
类字面常量不仅可以应用于普通类,也可以应用于接口、数组以及基本数据类型。
为使用类而做的三个步骤:
1、加载
查找字节码,并创建class对象
2、链接
验证字节码,为静态域分配空间
3、初始化
如果该类有超类,对其初始化,执行静态初始化器和静态初始化块
初始化有效的实现了尽可能的“惰性”,使用.class语法来获得对类的引用并不会引发初始化。
static final是编译期常量,获取这个值不需要对整个类初始化
static final的不是常量,而是随机数,那还是会对类进行初始化
static而不是final的,要先进行链接初始化,所以还是会对类初始化
泛化的Class引用
Class引用总是指向某个Class对象,表示的是它所指对象的确切类型。
使用泛型引用,可以限定class引用的类型,使其不能再指向其他类型,是的编译器强制执行额外的类型检查。普通的class引用没有这个限制。
Class c = Cat.class;
为了放松限制,可以使用通配符,符号为“?”,表示任何类型。使用通配符产生的class引用,可以指向其它类型。
使用“?extends BaseType”,可以创建一个范围,表示该类或该类的子类。
向Class添加泛型语法的原因是为了提供编译期类型检查。
将泛型语法用于class对象,newInstance()将返回该对象的确切类型。
Class c = Cat.class;
Cat mimi = c.newInstance();
新的转型语法
使用 要转成类型的对象 = cast(被转对象);
Cat c= new cat();
Pet pet = p.cast(c);
这个方法很繁琐,除了极端罕见情况外几乎用不到,毕竟平时使用
Pet pet = (Pet)c;
简单一句就可以了
3、类型转换前先做检查
执行错误的类型转换,会抛出ClassCastException。
IllegalAccessException表示违反Java安全机制。
向上转型可以不显式,向下转型要显式。
inatanceof只能与命名类型(就是类名)比较,不能与对象比较。
使用类的字面常量
package typeinfo.pets;
import java.util.*;
public class LiteralPetCreator extends PetCreator {
// No try block needed.
@SuppressWarnings("unchecked")
public static final List> allTypes =
Collections.unmodifiableList(Arrays.asList(
Pet.class, Dog.class, Cat.class, Rodent.class,
Mutt.class, Pug.class, EgyptianMau.class, Manx.class,
Cymric.class, Rat.class, Mouse.class,Hamster.class));
// Types for random creation:
private static final List> types =
allTypes.subList(allTypes.indexOf(Mutt.class),
allTypes.size());
public List> types() {
return types;
}
public static void main(String[] args) {
System.out.println(types);
}
} /* Output:
[class typeinfo.pets.Mutt, class typeinfo.pets.Pug, class
typeinfo.pets.EgyptianMau, class typeinfo.pets.Manx, class
typeinfo.pets.Cymric, class typeinfo.pets.Rat, class
typeinfo.pets.Mouse, class typeinfo.pets.Hamster]
动态的instanceof
为了防止一大串instanceof()比对找类型,可以使用 遍历寻找isInstance(对象)匹配来简化代码
public void count(Pet pet) {
// Class.isInstance() eliminates instanceofs:
for(Map.Entry,Integer> pair
: entrySet())
if(pair.getKey().isInstance(pet))
put(pair.getKey(), pair.getValue() + 1);
}
递归计数
使用Class.isAssignableFrom() 来校验传递的对象是否属于感兴趣的继承结构
package net.mindview.util;
import java.util.*;
public class TypeCounter extends HashMap,Integer>{
private Class> baseType;
public TypeCounter(Class> baseType) {
this.baseType = baseType;
}
public void count(Object obj) {
Class> type = obj.getClass();
if(!baseType.isAssignableFrom(type))
throw new RuntimeException(obj + " incorrect type: "
+ type + ", should be type or subtype of "
+ baseType);
countClass(type);
}
private void countClass(Class> type) {
Integer quantity = get(type);
put(type, quantity == null ? 1 : quantity + 1);
Class> superClass = type.getSuperclass();
if(superClass != null &&
baseType.isAssignableFrom(superClass)) //到最上层基类跳出递归
countClass(superClass);
}
public String toString() {
StringBuilder result = new StringBuilder("{");
for(Map.Entry,Integer> pair : entrySet()) {
result.append(pair.getKey().getSimpleName());
result.append("=");
result.append(pair.getValue());
result.append(", ");
}
result.delete(result.length()-2, result.length());
result.append("}");
return result.toString();
}
}
//: typeinfo/PetCount4.java
import typeinfo.pets.*;
import net.mindview.util.*;
import static net.mindview.util.Print.*;
public class PetCount4 {
public static void main(String[] args) {
TypeCounter counter = new TypeCounter(Pet.class);
for(Pet pet : Pets.createArray(20)) {
printnb(pet.getClass().getSimpleName() + " ");
counter.count(pet);
}
print();
print(counter);
}
} /* Output: (Sample)
Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster
EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric
{Mouse=2, Dog=6, Manx=7, EgyptianMau=2, Rodent=5, Pug=3, Mutt=3,
Cymric=5, Cat=9, Hamster=1, Pet=20, Rat=2}
4、注册工厂
使用工厂设计模式,避免每多一个类,就要往类字面常量列表手动里加一个类引用。
//: typeinfo/factory/Factory.java
package typeinfo.factory;
public interface Factory { T create(); }
工厂类接口,泛型,并使用create()使子类自己生成类对象
//: typeinfo/RegisteredFactories.java
// Registering Class Factories in the base class.
import typeinfo.factory.*;
import java.util.*;
class Part {
public String toString() {
return getClass().getSimpleName();
}
static List> partFactories =
new ArrayList>();
static { //类型列表,生成内部类,加载类
// Collections.addAll() gives an "unchecked generic
// array creation ... for varargs parameter" warning.
partFactories.add(new FuelFilter.Factory());
partFactories.add(new AirFilter.Factory());
partFactories.add(new CabinAirFilter.Factory());
partFactories.add(new OilFilter.Factory());
partFactories.add(new FanBelt.Factory());
partFactories.add(new PowerSteeringBelt.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();
}
}
class Filter extends Part {}
class FuelFilter extends Filter {
// Create a Class Factory for each specific type:
public static class Factory
implements typeinfo.factory.Factory { //Factory重名,需要用全限定类名表示接口类
public FuelFilter create() { return new FuelFilter(); }
}
}
class AirFilter extends Filter {
public static class Factory
implements typeinfo.factory.Factory {
public AirFilter create() { return new AirFilter(); }
}
}
class CabinAirFilter extends Filter {
public static class Factory
implements typeinfo.factory.Factory {
public CabinAirFilter create() {
return new CabinAirFilter();
}
}
}
class OilFilter extends Filter {
public static class Factory
implements typeinfo.factory.Factory {
public OilFilter create() { return new OilFilter(); }
}
}
class Belt extends Part {}
class FanBelt extends Belt {
public static class Factory
implements typeinfo.factory.Factory {
public FanBelt create() { return new FanBelt(); }
}
}
class GeneratorBelt extends Belt {
public static class Factory
implements typeinfo.factory.Factory {
public GeneratorBelt create() {
return new GeneratorBelt();
}
}
}
class PowerSteeringBelt extends Belt {
public static class Factory
implements typeinfo.factory.Factory {
public PowerSteeringBelt create() {
return new PowerSteeringBelt();
}
}
}
public class RegisteredFactories {
public static void main(String[] args) {
for(int i = 0; i < 10; i++)
System.out.println(Part.createRandom());
}
} /* Output:
GeneratorBelt
CabinAirFilter
GeneratorBelt
AirFilter
PowerSteeringBelt
CabinAirFilter
FuelFilter
PowerSteeringBelt
PowerSteeringBelt
FuelFilter
5、instanceof与Class的等价性
对象
instanceof 类名 和 对象1.
isInstance(对象2) 产生的结果一样
类引用.
equals(类引用) 和 类引用
==类引用 结果也一样
instanceof保持了类型的概念,包括派生类
== 比较实际的Class对象,没有考虑继承
6、反射:运行时类信息
1、在编译时,编译器必须知道所有需要通过RTTI来处理的类。
2、反射机制:用来检查可用的方法,返回方法名。(用于编译时无法获知对象类型的情况)
Java通过JavaBeans提供了基于构件的编程架构。
需要 远程方法调用(RMI:
Remote Method Invoke)的一个案例就是客户端服务器,分布式计算。
RTTI和反射真正的区别在于:
RTTI:编译器在编译时打开和检查.class文件。
反射机制:.class文件在编译时不可获取,在运行时打开和检查.class文件 (创建动态的代码时很有用)
类方法提取器
Class的getMethods()返回Method对象的数组,获取的是完全的,包括名字、输入参数和返回值。
getConstructors()方法返回Constructor对象数组,获取的也是完全的。
如果将类作为非public类,那么输出中就不再显示自动合成的默认构造器,因为自动合成的默认构造器会自动被赋予与类一样的访问权限。
7、动态代理
代理模式
代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
按照代理的创建时期,代理类可以分为两种。
静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理:在程序运行时,运用反射机制动态创建而成。
观察代码可以发现每一个代理类只能为一个接口服务,这样一来程序开发中必然会产生过多的代理,而且,所有的代理操作除了调用的方法不一样之外,其他的操作都一样,则此时肯定是重复代码。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能,那么此时就必须使用动态代理完成。
java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。
JDK动态代理中包含一个类和一个接口:
InvocationHandler接口:
public interface InvocationHandler {
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable;
}
参数说明:
Object proxy:指被代理的对象。
Method method:要调用的方法
Object[] args:方法调用时所需要的参数
可以将InvocationHandler接口的子类想象成一个代理的最终操作类,替换掉ProxySubject。
Proxy类:
Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法:
public static Object newProxyInstance(ClassLoader loader, Class>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
参数说明:
ClassLoader loader:类加载器
Class>[] interfaces:得到全部的接口
InvocationHandler h:得到InvocationHandler接口的子类实例
Ps:类加载器
在Proxy类中的newProxyInstance()方法中需要一个ClassLoader类的实例,ClassLoader实际上对应的是类加载器,在Java中主要有一下三种类加载器;
Booststrap ClassLoader:此加载器采用C++编写,一般开发中是看不到的;
Extendsion ClassLoader:用来进行扩展类的加载,一般对应的是jre\lib\ext目录中的类;
AppClassLoader:(默认)加载classpath指定的类,是最常使用的是一种加载器。
动态代理
与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。
参考自:http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html
8、空对象
对象为空,除了表示为Null以外,还可以使用空对象,所谓空对象,它占了个位置,但是却并不返回实际靠谱的数据,是null更智能化人性化的替代物。空对象与null相比最有用之处在于它更接近数据。可以用instanceof来探测对象只是Null还是Null还是NullObject,单例模式下(通常,空对象都是单例),还可以用equals()或者==来比较是否为NullObject。
空对象可以很方便的用于“虚位以待”的情况,比如即使在没有这个实际对象的情况下,想要输出“We don't have this person”而不是“null”时,可以使用空对象,当然要自己构建空对象类.
模拟对象与桩
空对象的逻辑变体是模拟对象和桩。后者加班可以传递实际信息的存活对象。
模拟对象往往是轻量级和自测试的,通常大量模拟对象被创建出来处理不同测试情况,
桩是返回桩数据,通常是重量级的,经常在测试自己被复用。
9、接口与类型信息
interface关键字的一种重要目标就是允许程序员隔离构件,进而降低耦合性。
为了避免客户端程序员将接口类对象向下转型,可以对实现使用包访问权限。
对Method对象或者域对象调用setAccessible(true)方法,可以获取各种甚至private权限的对象。
包访问权限的类C:
class C implements A {
public void f() { print("public C.f()"); }
public void g() { print("public C.g()"); }
void u() { print("package C.u()"); }
protected void v() { print("protected C.v()"); }
private void w() { print("private C.w()"); }
}
public class HiddenC {
public static A makeA() { return new C(); }
}
获取类C的方法:
import typeinfo.interfacea.*;
import typeinfo.packageaccess.*;
import java.lang.reflect.*;
public class HiddenImplementation {
public static void main(String[] args) throws Exception {
A a = HiddenC.makeA();
a.f();
System.out.println(a.getClass().getName());
// Compile error: cannot find symbol ‘C’:
/* if(a instanceof C) {
C c = (C)a;
c.g();
} */
// Oops! Reflection still allows us to call g():
callHiddenMethod(a, "g");
// And even methods that are less accessible!
callHiddenMethod(a, "u");
callHiddenMethod(a, "v");
callHiddenMethod(a, "w");
}
static void callHiddenMethod(Object a, String methodName)
throws Exception {
Method g = a.getClass().getDeclaredMethod(methodName);
g.setAccessible(true);
g.invoke(a);
}
} /* Output:
public C.f()
typeinfo.packageaccess.C
public C.g()
package C.u()
protected C.v()
private C.w()
如果将类隐藏为内部类,那么System.out.println(a.getClass().getName());的结果是 InnerA$C(A内部的C类)
如果将类隐藏为匿名类,那么System.out.println(a.getClass().getName());的结果是 AnonymousA$1(A内部的匿名类)
可以看出,对反射没有隐藏任何东西。
对final成员的修改是安全的,但是其实运行时系统会在不抛出异常的情况下不接受任何修改,所以实际上还是没改成。
10、总结
面向对象程序语言的目的是让我们在凡是可以使用的地方都使用多态机制,只在必要的时候使用RTTI。
RTTI有时能解决效率问题,如果某个对象应用在多态中非常缺乏效率,那么可以单独挑出来,使用RTTI,为其编写一段特别的代码提高效率。(比如专门写个if(a.isInstance(XX))){这么做})
最好先让程序运作起来,然后在考虑速度效率。
反射可以允许更加动态的编程风格。