文章目录
类型信息
RTTI(RunTime Type Information,运行时类型信息)能够在程序运行时发现和使用类型信息。
19.1为什么需要RTTI
在java中,所有类型转换的正确性检查都是在运行时进行的。这也正是RTTI的含义所在:在运行时,识别一个对象的类型。
19.2Class
对象
要理解RTTI在java中的工作原理,首先必须知道类型信息在运行时是如何表示的。这项工作是由称为
Class
对象的特殊对象完成的,它包含了与类有关的信息。
实际上,Class
对象就是用来创建该类所有常规对象的。Java使用Class
对象来实现RTTI,即便是类型转换这样的操作都是用Class
对象实现的。
类是程序的一部分,每个类都有一个Class
对象。换言之,每当编写并且编译了一个新类,就会产生一个Class
对象(更恰当的说,是被保存在一个同名的.class文件中)。为了生成这个类的对象,java虚拟机会先调用类加载器子系统把这个类加载到内存中。
类加载器子系统可能包含一条类加载器链,但有且只有一个原生类加载器,它是JVM实现的一部分。原生类加载器加载的是可信类(包括java API类)。它们通常是从本地盘加载的。在这条链中,通常不需要添加额外的类加载器,但是如果有特殊需求(例如以某种特殊的方式加载类,以支持web服务器应用,或者通过网络下载类),也可以挂载额外的类加载器。
所有的类都是第一次使用时动态加载到JVM中的,当程序创建第一个对类的静态成员的引用时,就会加载这个类。其实构造器也是类的静态方法,虽然构造器前面并没有static
关键字。所以,使用new
操作符创建类的新对象,这个操作也算作对类的静态成员引用。
因此,java程序在它开始运行之前并没有被完全加载,很多部分是在需要时才会加载。
类加载器首先会检查这个类的Class
对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名查找.class文件(如果有附加的类加载器,这时候可能就会在数据库中或者通过其它方式获得字节码)。这个类的字节码被加载后,JVM会对其进行验证,确保它没有损坏,并且不包含不良的java代码。
一旦某个类的Class
对象被载入内存,它就可以用来创建这个类的所有对象。
// 检查类加载器的工作方式
public class SweetShop {
public static void main(String[] args) {
System.out.println("inside main");
new Candy();
System.out.println("After creating Candy");
try {
Class.forName("Gum");
} catch (ClassNotFoundException e) {
System.out.println("Couldn't find Gum");
}
System.out.println("After Class.forName(\"Gum\")");
new Cookie();
System.out.println("After creating Cookie");
}
}
class Cookie {
static {
System.out.println("Loading Cookie");
}
}
class Gum {
static {
System.out.println("Loading Gum");
}
}
class Candy {
static {
System.out.println("Loading Candy");
}
}
// 输出结果:
// inside main
// Loading Candy
// After creating Candy
// Loading Gum
// After Class.forName("Gum")
// Loading Cookie
// After creating Cookie
19.2.1类字面常量
Java还提供了另一种方法来生成类对象的引用:类字面常量,例如:
FancyToy.class
。这样做不仅更简单,而且更安全,因为它在编译时就会受到检查(因此不必放在try
语句块中)。并且它根除了对forName()
方法的调用,所以效率更高。
类字面常量不仅可以应用于普通类,也可以应用于接口、数组以及基本数据类型。另外,对于基本数据类型的包装类,还有一个标准字段TYPE
。TYPE
字段是一个引用,指向对应的基本数据类型的Class
对象。
需要注意的是,当使用.class
来创建对Class
对象的引用时,不会自动地初始化该Class
对象。为了使用类而做的准备工作实际包含三个步骤:
- 加载,这是由类加载器执行的。该步骤将查找字节码(通常在classpath所指定的路径中查找,但这并非是必须的),并从这些字节码中创建一个
Class
对象。- 链接。在链接阶段将验证类中的字节码,为
static
字段分配存储空间,并且如果需要的话,将解析这个类创建的对其他类的所有引用。- 初始化。如果该类具有超类,则先初始化超类,执行
static
初始化器和static
初始化块。直到第一次引用一个
static
方法(构造器隐式地是static
)或者非常量的static
字段,才会进行类初始化。
public class ClassInitialization {
public static Random rand = new Random(47);
public static void main(String[] args) throws Exception {
// 初始化有效地实现了尽可能的惰性,仅使用.class语法来获得对类对象的引用不会引发初始化
Class initable = Initable.class;
System.out.println("After creating Initable ref");
// Does not trigger initialization
// 如果一个static final值是编译期常量,那么这个值不需要对类进行初始化就可以被读取
System.out.println(Initable.STATIC_FINAL);
// Does trigger initialization
// 但是,如果只是将一个字段设置成为static和final,还不足以确保这种行为
System.out.println(Initable.STATIC_FINAL2);
// Does trigger initialization
System.out.println(Initable2.staticNonFinal);
// 使用Class.forName()会立即进行初始化
Class initable3 = Class.forName("test.part5.Initable3");
System.out.println("After creating Initable3 ref");
// 如果一个static字段不是final的,那么在对它访问时,总是要求在它被读取之前,
// 首先进行链接(为这个字段分配存储空间)和初始化(初始化该存储空间)
System.out.println(Initable3.staticNonFinal);
}
}
class Initable {
static final int STATIC_FINAL = 47;
static final int STATIC_FINAL2 = ClassInitialization.rand.nextInt(1000);
static {
System.out.println("Initializing Initable");
}
}
class Initable2 {
static int staticNonFinal = 47;
static {
System.out.println("Initializing Initable2");
}
}
class Initable3 {
static int staticNonFinal = 74;
static {
System.out.println("Initializing Initable3");
}
}
// 输出结果:
// After creating Initable ref
// 47
// Initializing Initable
// 258
// Initializing Initable2
// 47
// Initializing Initable3
// After creating Initable3 ref
// 74
19.2.2泛型的Class
引用
public class GenericClassReferences {
public static void main(String[] args) {
Class intClass = int.class;
Class<Integer> genericIntClass = int.class
genericIntClass = Integer.class; // 同一个东西
intClass = double.class;
// genericIntClass = double.class; // 非法
}
}
通过使用泛型语法,可以让编译器强制执行额外的类型检查。
Class<Number> geenericNumberClass = int.class;
这看起来似乎是起作用的,因为
Integer
继承自Number
。但事实却是不行,因为Integer
的Class
对象并不是Number
的Class
对象的子类。
为了创建一个限定指向某种类型或其子类的Class
引用,需要将通配符与extends
关键字配合使用,创建一个范围限定。
public class BoundedClassReferences {
public static void main(String[] args) {
Class<? extends Number> bounded = int.class;
bounded = double.class;
bounded = Number.class;
// Or anything else derived from Number
}
}
当将泛型语法用于
Class
对象时,newInstance()
将返回该对象的确切类型。然而,这在某种程度上有些受限:
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();
}
}
如果你手头的是超类,那编译器将只允许你声明超类引用为“某个类,它是
FancyToy
的超类”,就像在表达式Class<? super FancyToy>
中所看到的那样,而不会接收Class<Toy>
这样的声明。这看上去显得有些怪,因为getSuperClass()
方法返回的是基类(不是接口),并且编译器在编译期就知道它是什么类型了(在本例中就是Toy.class
),而不仅仅只是“某个类”。不管怎样,正是由于这种含糊性,up.newInstance
的返回值不是精确类型,而只是Object
。
19.3类型转换检测
已知的RTTI类型包括:
- 传统的类型转换,如
(Shape)
,由RTTI确保转换的正确性,如果执行了一个错误的类型转换,就会抛出一个ClassCastException
异常。- 代表对象类型的
Class
对象。通过查询Class
对象可以获取运行时所需的信息。- 关键字
instanceof
返回一个布尔值,告诉对象是不是某个特定类型的实例。一般,可能想要查找某种类型,这时可以轻松地使用instanceof
来度量所有对象。
// 每个Individual都有一个id()方法,如果没有为Individual提供名字,
// toString()方法只产生类名
public class Person extends Individual {
public Person(String name) {
super(name);
}
}
public class Pet extends Individual {
public Pet(String name) {
super(name);
}
public Pet() {
super();
}
}
public class Dog extends Pet {
public Dog(String name) {
super(name);
}
public Dog() {
super();
}
}
// ...其他类和Dog大致一样,详见图
// 随机地创建不同类型的Pet,同事,还可以创建Pet数组和持有Pet的List,
// 为了使这个类更加普遍适用,将其定义为抽象类
public abstract class PetCreator implements Supplier<Pet> {
private Random rand = new Random(47);
// The List of the different types of Pet to create:
public abstract List<Class<? extends Pet>> types();
// Create one random Pet
public Pet get() {
int n = rand.nextInt(types().size());
try {
return types().get(n).newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
// PetCreator的实现类
public class ForNameCreator extends PetCreator {
// 需要随机生成的类型名:
private static List<Class<? extends Pet>> types = new ArrayList<>();
private static String[] typeNames = { "test.part12.Mutt", "test.part12.Pug",
"test.part12.EgyptianMau", "test.part12.Manx", "test.part12.Cymric",
"test.part12.Rat", "test.part12.Mouse", "test.part12.Hamster" };
// 该注解不能直接放置在静态代码块上,因此分开编写
@SuppressWarnings("unchecked")
private static void loader() {
try {
for (String name : typeNames) {
types.add((Class<? extends Pet>) Class.forName(name));
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
static {
loader();
}
@Override
public List<Class<? extends Pet>> types() {
return types;
}
}
// 对Pet进行计数
public class PetCount {
static class Counter extends HashMap<String, Integer> {
public void count(String type) {
Integer quantity = get(type);
if (quantity == null) {
put(type, 1);
} else {
put(type, quantity + 1);
}
}
}
public static void countPets(PetCreator creator) {
Counter counter = new Counter();
// List each individual pet:
// Pets.array()产生随机Pet集合
for (Pet pet : Pets.array(20)) {
System.out.print(pet.getClass().getSimpleName() + " ");
// instanceof有一个严格的限制:只可以将它与命名类型进行比较,
// 而不能与Class对象作比较
if (pet instanceof Pet) {
counter.count("Pet");
}
if (pet instanceof Dog) {
counter.count("Dog");
}
if (pet instanceof Mutt) {
counter.count("Mutt");
}
if (pet instanceof Pug) {
counter.count("Pug");
}
if (pet instanceof Cat) {
counter.count("Cat");
}
if (pet instanceof EgyptianMau) {
counter.count("EgyptianMau");
}
if (pet instanceof Manx) {
counter.count("Manx");
}
if (pet instanceof Cymric) {
counter.count("Cymric");
}
if (pet instanceof Rodent) {
counter.count("Rodent");
}
if (pet instanceof Rat) {
counter.count("Rat");
}
if (pet instanceof Mouse) {
counter.count("Mouse");
}
if (pet instanceof Hamster) {
counter.count("Hamster");
}
}
// Show the counts:
System.out.println();
System.out.println(counter);
}
public static void main(String[] args) {
countPets(new ForNameCreator());
}
}
19.3.1使用类字面量
// 如果使用类字面量重新实现PetCreator类的话,其结果在很多方面都会更清楚
public class LiteralPetCreator extends PetCreator {
// try代码块不再需要,因为它是编译时计算的,不会引发任何异常
public static final List<Class<? extends Pet>> ALL_TYPES = 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
)
); // 用于随机创建的类型:
private static final List<Class<? extends Pet>> TYPES = ALL_TYPES.subList(
ALL_TYPES.indexOf(Mutt.class), ALL_TYPES.size()
);
@Override
public List<Class<? extends Pet>> types() {
return TYPES;
}
public static void main(String[] args) {
System.out.println(TYPES);
}
}
public class Pets {
// 创建一个使用LiteralPetCreator的外观模式
public static final PetCreator CREATOR = new LiteralPetCreator();
public static Pet get() {
return CREATOR.get();
}
public static Pet[] array(int size) {
Pet[] result = new Pet[size];
for (int i = 0; i < size; i++) {
result[i] = CREATOR.get();
}
return result;
}
public static List<Pet> list(int size) {
List<Pet> result = new ArrayList<>();
Collections.addAll(result, array(size));
return result;
}
public static Stream<Pet> stream() {
return Stream.generate(CREATOR);
}
}
public class PetCount2 {
public static void main(String[] args) {
PetCount.countPets(Pets.CREATOR);
}
}
19.3.2一个动态instanceof
函数
Class.isInstance()
方法提供了一种动态测试对象类型的方法。
public class PetCount3 {
static class Counter extends LinkedHashMap<Class<? extends Pet>, Integer> {
Counter() {
super(
LiteralPetCreator.ALL_TYPES.stream()
.map(lpc -> Pair.make(lpc, 0))
.collect(Collectors.toMap(Pair::key, Pair::value))
);
}
public void count(Pet pet) {
// Class.isInstance()替换instanceof
entrySet().stream()
.filter(pair -> pair.getKey().isInstance(pet))
.forEach(pair -> put(pair.getKey(), pair.getValue() + 1));
}
@Override
public String toString() {
String result = entrySet().stream()
.map(pair -> String.format("%s=%s",
pair.getKey().getSimpleName(),
pair.getValue())
)
.collect(Collectors.joining(", "));
return "{" + result + "}";
}
}
public static void main(String[] args) {
Counter petCount = new Counter();
Pets.stream()
.limit(20)
.peek(petCount::count)
.forEach(p -> System.out.print(p.getClass().getSimpleName() + " "));
System.out.println("n" + petCount);
}
}
19.3.3递归计数
// PetCount3.Counter中的Map预先加载了所有不同的Pet类。
// 可以使用Class.isAssignableFrom()而不是预加载Map,
// 并创建一个不限于计数Pet的通用工具
public class TypeCounter extends HashMap<Class<?>, Integer> {
private Class<?> baseType;
public TypeCounter(Class<?> baseType) {
this.baseType = baseType;
}
public void count(Object obj) {
Class<?> type = obj.getClass();
// 使用isAssignableFrom进行运行时检查,以验证传递的对象的类型是否是感兴趣的层次结构
if (!baseType.isAssignableFrom(type)) {
throw new RuntimeException(obj + " incorrect type: " + type +
", should be type or subtype of " + baseType);
}
countClass(type);
}
// 首先计算类的确切类型。然后,如果baseType可以从超类赋值,则在超类上递归调用countClass()
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);
}
}
@Override
public String toString() {
String result = entrySet().stream()
.map(pair -> String.format("%s=%s",
pair.getKey().getSimpleName(),
pair.getValue())
)
.collect(Collectors.joining(", "));
return "{" + result + "}";
}
}
public class PetCount4 {
public static void main(String[] args) {
TypeCounter counter = new TypeCounter(Pet.class);
Pets.stream()
.limit(20)
.peek(counter::count)
.forEach(p -> System.out.print(p.getClass().getSimpleName() + " "));
System.out.println("n" + counter);
}
}
19.4注册工厂
从
Pet
层次结构生成对象的问题是,每当向层次结构中添加一种新类型的Pet
时,必须记住将其添加到LiteralPetCreator
的条目中。在一个定期添加更多类的系统中,这可能会成为问题。
可能会考虑向每个子类添加静态初始值设定项,因此初始值设定项会将其类添加到某个列表中。不幸的是,静态初始值设定项仅在首次加载类时调用,因此存在鸡和蛋的问题:生成器的列表中没有类,因此它无法创建该类的对象,因此类不会被加载并放入列表中。
基本上,必须自己手动创建列表(除非编写了一个工具来搜索和分析源代码,然后创建和编译列表)。所以能做的最好的事情就是把列表集中放在一个明显的地方。层次结构的基类可能是最好的地方。
// 使用工厂方法设计模式将对象的创建推迟到类本身。工厂方法可以以多态的方式调用,
// 并创建适当类型的对象。事实证明,Supplier用T get()描述了原型工厂方法,
// 协变返回类型允许get()为Supplier的每个子类实现返回不同的类型
public class RegisteredFactories {
public static void main(String[] args) {
Stream.generate(new Part())
.limit(10)
.forEach(System.out::println);
}
}
class Part implements Supplier<Part> {
static List<Supplier<? extends Part>> prototypes = Arrays.asList(
new FuelFilter(), new AirFilter(), new CabinAirFilter(), new OilFilter(), new FanBelt(), new PowerSteeringBelt(),
new GeneratorBelt());
private static Random rand = new Random(47);
@Override
public String toString() {
return getClass().getSimpleName();
}
// 并非层次结构中的所有类都应实例化,这里的Filter和Belt只是分类器,
// 这样就只创建它们的子类
@Override
public Part get() {
int n = rand.nextInt(prototypes.size());
return prototypes.get(n).get();
}
}
class Filter extends Part {
}
class FuelFilter extends Filter {
@Override
public Part get() {
return new FuelFilter();
}
}
class AirFilter extends Filter {
@Override
public Part get() {
return new AirFilter();
}
}
class CabinAirFilter extends Filter {
@Override
public Part get() {
return new CabinAirFilter();
}
}
class OilFilter extends Filter {
@Override
public Part get() {
return new OilFilter();
}
}
class Belt extends Part {
}
class FanBelt extends Belt {
@Override
public Part get() {
return new FanBelt();
}
}
class GeneratorBelt extends Belt {
@Override
public Part get() {
return new GeneratorBelt();
}
}
class PowerSteeringBelt extends Belt {
@Override
public Part get() {
return new PowerSteeringBelt();
}
}
19.6反射:运行时类信息
当使用反射与未知类型的对象交互时,JVM将查看该对象,并看到它属于特定的类(就像普通的RTTI)。在对其执行任何操作之前,必须加载
Class
对象。因此,该特定类型的.class文件必须在本地计算机上或通过网络对JVM仍然可用。因此,RTTI和反射的真正区别在于,使用RTTI时,编译器在编译时会打开并检查.class文件。换句话说,可以用正常的方式调用一个对象的所有方法。通过反射,.class文件在编译时不可用:它由运行时环境打开并检查。
19.7动态代理
代理是基本的设计模式之一。一个对象封装真实对象,代替其提供其他或不同的操作,这些操作通常涉及到与真实对象的通信,因此代理通常充当中间对象。
public 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()));
}
}
interface Interface {
void doSomething();
void somethingElse(String arg);
}
class RealObject implements Interface {
@Override
public void doSomething() {
System.out.println("doSomething");
}
@Override
public void somethingElse(String arg) {
System.out.println("somethingElse " + arg);
}
}
class SimpleProxy implements Interface {
private Interface proxied;
SimpleProxy(Interface proxied) {
this.proxied = proxied;
}
@Override
public void doSomething() {
System.out.println("SimpleProxy doSomething");
proxied.doSomething();
}
@Override
public void somethingElse(String arg) {
System.out.println("SimpleProxy somethingElse " + arg);
proxied.somethingElse(arg);
}
}
当希望将额外的操作与真实对象做分离时,代理可能会有所帮助,尤其是想要轻松地启用额外的操作时,反之亦然(设计模式就是封装变更,所以必须改变一些东西以证明模式的合理性)。
Java的动态代理更进一步,不仅动态创建代理对象而且动态处理对代理方法的调用。在动态代理上进行的所有调用都被重定向到单个调用处理程序,该处理程序负责发现调用的内容并决定如何处理。
public 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);
}
}
class DynamicProxyHandler implements InvocationHandler {
private Object proxied;
DynamicProxyHandler(Object proxied) {
this.proxied = proxied;
}
@Override
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);
}
}
19.8Optional
类
实际上,在所有地方都使用
Optional
是没有意义的,有时候检查一下是不是null
也挺好的,或者有时可以合理地假设不会出现null
,甚至有时候检查NullPointException
也是可以接受的。Optional
最有用武之地的是在那些更接近数据的地方,在问题空间中代表实体的对象上。