迄今为止,我们已知的RTTI形式包括:
- 传统的类型转换,如“(Shape)”,由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出一个ClassCaseException。
- 代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息。
在C++中,经典的类型转换“(Shape)”并不使用RTTI。它只是简单地告诉编译器将这个对象作为新的类型对待。而java要执行类型检查,这通常被称为“类型安全的向下转型”。之所以叫“向下转型”,是由于类层次结构图从来就是这么排列的。如果将Circle类转换为Shape类型被称作向上转型,那么将Shape转型为Circle,就被称为向下转型。但是,由于知道Circle肯定是一个Shape,所以编译器允许自由地做向上转型的赋值操作,而不需要任何显式的转型操作。编译器无法知道对于给定的Shape到底是什么Shape——它可能就是Shape,或者是Shape的子类型,例如Circle、Square、Traingle或某种其他的类型。在编译期。编译器只能知道它是Shape。因此,如果不使用显式的类型转换,编译器就不允许你执行向下转型赋值,以告知编译器你拥有额外的信息,这些信息使你知道该类型是某种特定类型(编译器将检查向下转型是否合理,因此它不允许向下转型到实际上不是待转型类的子类的类型上)。
RTTI在java中还有第三种形式,就是关键字instanceof。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例。可以用提问的方式使用它,就像这样:
if(x instanceof Dog)
((Dog)x).bark();
在将x转型成一个Dog前,上面的if语句会检查对象x是否从属于Dog类。进行向下转型前,如果没有其他信息可以告诉你这个对像是什么类型,那么使用instanceof是非常重要的,否则会得到一个ClassCastException异常。
一般,可能想要查找某种类型(比如要找三角形,并填充成紫色),这时可以轻松地使用instanceof来计数所有对象。例如,假设你有一个类的继承体系,描述了Pet(以及他们的主人,这是在后面的示例中出现的一个非常方便的特性)。这个继承体系中的每个Individual都有一个id和一个可选名字。 尽管下面的类都继承自Individual,但是Individual类复杂性较高,此处并不需要去了解Individual的代码——你只需要了解你可以创建其具名或不具名的对象,并且每个Individual都有一个id()方法,可以返回其唯一的标识符(通过对每个对象计数而创建的)。还有一个toString()方法,如果你没有为Individual提供名字,toString()方法只产生类型名字。
/**
* 个别的
*/
public class Individual implements Comparable<Individual> {
private static long counter = 0;
private final long id = counter++;
private String name;
public Individual(String name) {
this.name = name;
}
public Individual() {
}
public String toString() {
return getClass().getSimpleName() + (name == null ? "" : "" + name);
}
public long id() {
return id;
}
public boolean equals(Object o) {
return o instanceof Individual && id == ((Individual) o).id;
}
public int hashCode() {
int result = 17;
if (name != null)
result = 37 * result + name.hashCode();
result = 37 * result + (int) id;
return result;
}
@Override
public int compareTo(Individual arg) {
String first = getClass().getSimpleName();
String argFirst = arg.getClass().getSimpleName();
int firstCompare = first.compareTo(argFirst);
if (firstCompare != 0)
return firstCompare;
if (name != null && arg.name != null) {
int secondCompare = name.compareTo(arg.name);
if (secondCompare != 0)
return secondCompare;
}
return (arg.id < id ? -1 : (arg.id == id ? 0 : 1));
}
}
/**
* 人
*/
public class Person extends Individual {
public Person(String name) {
super(name);
}
}
/**
* 宠物
*/
public class Pet extends Individual {
public Pet(String name) {
super(name);
}
public Pet() {
}
}
/**
* 狗
*/
public class Dog extends Pet {
public Dog(String name) {
super(name);
}
public Dog() {
super();
}
}
/**
* 杂种狗
*/
public class Mutt extends Dog {
public Mutt(String name) {
super(name);
}
public Mutt() {
super();
}
}
/**
* 哈巴狗
*/
public class Pug extends Dog {
public Pug(String name) {
super(name);
}
public Pug() {
super();
}
}
/**
* 猫
*/
public class Cat extends Pet {
public Cat(String name) {
super(name);
}
public Cat() {
super();
}
}
/**
* 埃及猫
*/
public class EgyptianMau extends Cat {
public EgyptianMau(String name) {
super(name);
}
public EgyptianMau() {
super();
}
}
/**
* 马恩岛猫
*/
public class Manx extends Cat {
public Manx(String name) {
super(name);
}
public Manx() {
super();
}
}
/**
* 威尔士猫
*/
public class Cymric extends Manx {
public Cymric(String name) {
super(name);
}
public Cymric() {
super();
}
}
/**
* 啮齿目动物
*/
public class Rodent extends Pet {
public Rodent(String name) {
super(name);
}
public Rodent() {
super();
}
}
/**
* 鼠
*/
public class Rat extends Rodent {
public Rat(String name) {
super(name);
}
public Rat() {
super();
}
}
/**
* 老鼠
*/
public class Mouse extends Rodent {
public Mouse(String name) {
super(name);
}
public Mouse() {
super();
}
}
/**
* 仓鼠
*/
public class Hamster extends Rodent {
public Hamster(String name) {
super(name);
}
public Hamster() {
super();
}
}
接下来,我们需要一种方法,通过它可以随机地创建不同类型的宠物,并且为方便起见,还可以创建宠物数组和List。为了使该工具能够适应多种不同的实现,我们将其定义为抽象类:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
public abstract class PetCreator {
private Random r = new Random();
public abstract List<Class<? extends Pet>> types();
public Pet randomPet() {
int n = r.nextInt(types().size());
try {
return types().get(n).newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public Pet[] createArray(int size) {
Pet[] result = new Pet[size];
for (int i = 0; i < size; i++)
result[i] = randomPet();
return result;
}
public ArrayList<Pet> arrayList(int size) {
ArrayList<Pet> result = new ArrayList<>();
Collections.addAll(result, createArray(size));
return result;
}
}
抽象的getType()方法在导出类中实现,以获取由Class对象构成的List(这是模版方法设计模式的一种变体)。注意,其中类的类型被指定为“任何从Pet导出的类”,因此newInstance()不需要转型就可以产生Pet。randomPet()随机地产生List中的索引,并使用被选取的Class对象,通过Class.newInstance()来生成该类的新实例。createArray()方法使用randomPet()来填充数组,而ArrayList()方法使用的则是createArray()。
在调用newInstance()时,可能会得到两种异常,在紧跟try语句块后面的catch子句中可以看到对它们的处理。异常的名字再次成为了一种对错误类型相对比较有用的解释(IllegalAccessException表示违反了java安全机制,在本例中,表示默认构造器为private的情况)。
当你导出PetCreator的子类时,唯一所需提供的就是你希望使用randomPet()和其他方法来创建的宠物类型的List。getTypes()方法通常只返回对一个静态List的引用。下面是使用forName()的一个具体实现:
import java.util.ArrayList;
import java.util.List;
public class ForNameCreator extends PetCreator {
private static List<Class<? extends Pet>> types = new ArrayList<>();
private static String[] typeNames = { "typeinfo.pets.Mutt", "typeinfo.pets.Pug", "typeinfo.pets.EgyptianMau",
"typeinfo.pets.Manx", "typeinfo.pets.Cymric", "typeinfo.pets.Rat", "typeinfo.pets.Mouse",
"typeinfo.pets.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;
}
}
loader()方法用Class.forName()创建了Class对象的List,这可能会产生ClassNotFoundException异常,这么做是有意义的,因为你传递给它的是一个在编译期无法验证的String。由于Pet对象在typeinfo包中,因此必须使用报名来引用这些类。
为了产生具有实际类型的Class对象的List,必须使用转型,这会产生编译期警告。loader()方法被单独定义,然后被置于一个静态初始化子句中,因为@SuppressWarnings注解不能直接置于静态初始化子句之上。
为了对Pet进行计数,我们需要一个能跟踪各种不同类型的Pet的数量的工具。Map是此需求的首选,其中键是Pet类型名,而值是保存Pet数量的Integer。通过这种方式,你可以询问:“有多少个Hamster对象?”我们可以使用instanceof来对Pet进行计数:
import java.util.HashMap;
import typeinfo.pets.*;
public class PetCount {
static class PetCounter 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) {
PetCounter counter = new PetCounter();
for (Pet pet : creator.createArray(20)) {
System.out.print(pet.getClass().getSimpleName() + " ");
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 Manx)
counter.count("EgyptianMau");
if (pet instanceof Manx)
counter.count("Manx");
if (pet instanceof Manx)
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");
}
System.out.println();
System.out.println(counter);
}
public static void main(String[] args) {
countPets(new ForNameCreator());
}
}
在CountPets()中,是使用PetCreator来随机地向数组中填充Pet的。然后使用instanceof对该数组中的每个Pet进行测试和计数。
对instanceof有比较严格的限制,只可将其与命名类型进行比较,而不能与Class对象作比较。在前面的例子中,可能觉得写出那么一大堆instanceof表达式是很乏味的,的确如此。但是也没有办法让instanceof聪明起来,让它能够自动地创建一个Class对象的数组,然后将目标对象与这这个数组中的对象进行逐一的比较(稍后你会看到一个替代方案)。其实这并非是一种如你想象中那般好的限制,因为渐渐的就会理解,如果程序中编写了许多的instanceof表达式,就说明你的设计可能存在瑕疵。
如果本文对您有很大的帮助,还请点赞关注一下。