迄今为止,我们已知的 RTTI 形式包括:
1. 经典的类型转换,如"(Shape)",由 RTTI 确保类型转换的正确性,如果你执行了
一个错误的类型转换,就会抛出一个 ClassCastException 异常。
2. 代表对象类型的 Class 对象。通过查询 Class 对象可以获取运行期所需的信息。
在 C++中,经典的类型转换"(Shape)"并不使用 RTTI。它只是简单地告诉编译器将这个对
象作为新的类型对待。而 Java 要执行类型检查,这通常被称为“类型安全的向下转型(type
safe downcast)”。之所以叫“向下转型”,是由于类层次结构图从来就是这么排列的。如果
将 Circle 类型转换为 Shape 被称作向上转型,那将 Shape 转型为 Circle,就被称为向下转型。
你知道 Circle 肯定是一个 Shape,所以编译器允许自由地做向上转型的赋值操作;然而,你
却不能保证一个 Shape 肯定是 Circle,因此,如果不显式做类型转换,编译器是不会自动做
向下转型操作的。
RTTI 在 Java 中还有第三种形式,就是关键字 instanceof。它返回一个布尔值,告诉我们对象
是不是某个特定类型的实例。你可以用提问的方式使用它,就象这样:
if(x instanceof Dog)
((Dog)x).bark();
在将 x 转型成一个 Dog 前,上面的 if 语句会检查对象 x 是否从属于 Dog 类。进行向下转
型前,如果没有其他信息可以告诉你这个对象是什么类型,那么使用 instanceof 是非常
重要的,否则会得到一个 ClassCastException 异常。
通常,你可能要查找一种类型(比如要找三角形,并填充成紫色),这时你可以轻松的使用
instanceof 来查找。假设你有一个 Pet 类的继承体系:
//: c10:Pet.java
package c10;
public class Pet {} ///:~
//: c10:Dog.java
package c10;
public class Dog extends Pet {} ///:~
//: c10:Pug.java
package c10;
public class Pug extends Dog {} ///:~
//: c10:Cat.java
package c10;
public class Cat extends Pet {} ///:~
//: c10:Rodent.java
package c10;
public class Rodent extends Pet {} ///:~
//: c10:Gerbil.java
package c10;
public class Gerbil extends Rodent {} ///:~
//: c10:Hamster.java
package c10;
public class Hamster extends Rodent {} ///:~
接下来的例子中,我们想跟踪每种特定类型的 Pet 的数量,因此我们需要一个类用来将这个
数量存储为一个 Int 类型,你可以把它看作是一个可修改的 Integer 对象:
//: c10:Counter.java
package c10;
public class Counter {
int i;
public String toString() { return Integer.toString(i); }
} ///:~
然后我们需要一个工具,以同时保存两件东西:一个是 Pet 具体类型的指示器,另一个是当
前 pet 具体类型的对象数量的计数器。也就是说,我们希望能够表达:“这里有多少个 Gerbil
对象”。普通的数组在这里是不行的,因为在数组里我们只能通过索引来查询对象。而这里
我们需要通过具体的 Pet 类型名来查找对象。还要把计数器对象与表示 Pet 具体类型的对象
关联起来。我们将使用一种标准的数据结构,“关联型数组(associative array)”。下面是一
个非常简单的版本:
//: c10:AssociativeArray.java
// Associates keys with values.
package c10;
import com.bruceeckel.simpletest.*;
public class AssociativeArray {
private static Test monitor = new Test();
private Object[][] pairs;
private int index;
public AssociativeArray(int length) {
pairs = new Object[length][2];
}
public void put(Object key, Object value) {
if(index >= pairs.length)
throw new ArrayIndexOutOfBoundsException();
pairs[index++] = new Object[] { key, value };
}
public Object get(Object key) {
for(int i = 0; i < index; i++)
if(key.equals(pairs[i][0]))
return pairs[i][1];
throw new RuntimeException("Failed to find key");
}
public String toString() {
String result = "";
for(int i = 0; i < index; i++) {
result += pairs[i][0] + " : " + pairs[i][1];
if(i < index - 1) result += "\n";
}
return result;
}
public static void main(String[] args) {
AssociativeArray map = new AssociativeArray(6);
map.put("sky", "blue");
map.put("grass", "green");
map.put("ocean", "dancing");
map.put("tree", "tall");
map.put("earth", "brown");
map.put("sun", "warm");
try {
map.put("extra", "object"); // Past the end
} catch(ArrayIndexOutOfBoundsException e) {
System.out.println("Too many objects!");
}
System.out.println(map);
System.out.println(map.get("ocean"));
monitor.expect(new String[] {
"Too many objects!",
"sky : blue",
"grass : green",
"ocean : dancing",
"tree : tall",
"earth : brown",
"sun : warm",
"dancing"
});
}
} ///:~
首 先 你 可 能 会 发 现 , 关 联 型 数 组 看 起 来 像 是 个 通 用 型 的 工 具 , 为 什 么 不 把 它 放 入
com.bruceeckel.tools 包中呢?确实,它是一个很有用的通用型的工具。实际上,
java.util 中包含了好几个关联型数组(也称作映射 map),它们可比我们用的这个数组
功能还要强大得多,并且执行速度更快。第十一章中将用很大的篇幅来讲解关联型数组,它
们相当复杂。所以我们只使用这个简单的版本,同时也使你熟悉关联型数组的用处。
在一个关联型数组中,索引也称作“键(key)”,关联的对象称作“值(value)”。我们通
过把键与值作为“键值对”存入一个二元素数组构成的二维数组中,来将它们关联在一起,
这也就是你在程序中看到的 pairs。这是一个固定长度的数组,所以我们使用 index 以保
证不会越界。当你用 put()方法添加键值对时,就生成一个新的只有两个元素的数组,并
添加在 pairs 中的下一个可用的位置。如果 index 大于或等于 pairs 的长度,就会抛出
异常。
使用 get()方法,你只需将要查找的键传给它,它返回与键相关联的值作为结果,或者在
没有找到相关联的值的情况下抛出异常。get()所使用的定位值的方法可能是你所能想象得
到的最低效的一种:从数组顶端开始,依次调用 equals()来比较键。但现在我们所关心的
是简单性,而不是高效性,而且第十一章中的真正的映射(map)已经解决了执行效率的问
题,所以现在我们不需要为此而担心。
关联型数组中基本的方法就是 put()和 get(),但是为了便于显示,toString()被重载
用来打印键值对。为了表明它是如何工作的,main()先向 AssociativeArray 中加载一
些键值对,然后把映射打印出来,最后还使用了 get()获取一个特定值。
现在所有的工具都备齐了,我们能使用 instanceof 来给 Pet 计数了。
//: c10:PetCount.java
// Using instanceof.
package c10;
import com.bruceeckel.simpletest.*;
import java.util.*;
public class PetCount {
private static Test monitor = new Test();
private static Random rand = new Random();
static String[] typenames = {
"Pet", "Dog", "Pug", "Cat",
"Rodent", "Gerbil", "Hamster",
};
// Exceptions thrown to console:
public static void main(String[] args) {
Object[] pets = new Object[15];
try {
Class[] petTypes = {
Class.forName("c10.Dog"),
Class.forName("c10.Pug"),
Class.forName("c10.Cat"),
Class.forName("c10.Rodent"),
Class.forName("c10.Gerbil"),
Class.forName("c10.Hamster"),
};
for(int i = 0; i < pets.length; i++)
pets[i] = petTypes[rand.nextInt(petTypes.length)]
.newInstance();
} catch(InstantiationException e) {
System.out.println("Cannot instantiate");
System.exit(1);
} catch(IllegalAccessException e) {
System.out.println("Cannot access");
System.exit(1);
} catch(ClassNotFoundException e) {
System.out.println("Cannot find class");
System.exit(1);
}
AssociativeArray map =
new AssociativeArray(typenames.length);
for(int i = 0; i < typenames.length; i++)
map.put(typenames[i], new Counter());
for(int i = 0; i < pets.length; i++) {
Object o = pets[i];
if(o instanceof Pet)
((Counter)map.get("Pet")).i++;
if(o instanceof Dog)
((Counter)map.get("Dog")).i++;
if(o instanceof Pug)
((Counter)map.get("Pug")).i++;
if(o instanceof Cat)
((Counter)map.get("Cat")).i++;
if(o instanceof Rodent)
((Counter)map.get("Rodent")).i++;
if(o instanceof Gerbil)
((Counter)map.get("Gerbil")).i++;
if(o instanceof Hamster)
((Counter)map.get("Hamster")).i++;
}
// List each individual pet:
for(int i = 0; i < pets.length; i++)
System.out.println(pets[i].getClass());
// Show the counts:
System.out.println(map);
monitor.expect(new Object[] {
new TestExpression("%% class c10\\."+
"(Dog|Pug|Cat|Rodent|Gerbil|Hamster)",
pets.length),
new TestExpression(
"%% (Pet|Dog|Pug|Cat|Rodent|Gerbil|Hamster)" +
" : \\d+", typenames.length)
});
}
} ///:~
在 main()中,用 Class.forName()创建了一个名为 petTypes 的 Class 对象的数组。
由于 Pet 对象属于 c10 这个包(package),因此命名的时候要把 package 的名字也包括
进去。
接 下 来 是 填 充 pets 数 组 。 从 petTyeps 中 随 机 选 择 Class 对 象 , 并 通 过
Class.newInstance()调用缺省的(无参数)构造器生成新的对象,加入到 pets 数组
中。
forName()与 newInstance()都可能会抛出异常,你可以在 try 语句块后紧跟着的
catch 子句中看到是如何处理这些异常的。记住,异常的名字对发生的错误是相当有用的
解释(例如 IllegalAccessException 表示违背了 Java 的安全机制)。
在创建了 AssociativeArray 之后,它就被填充满了由 pet 名字和数量组成的键值对。
在这个随机生成的数组中每个 Pet 都是通过使用 instanceof 来检测并计数的。这个数组
和 AssociativeArray 都被打印了出来,这样你就可以比较结果了。
对 instanceof 有比较严格的限制:你只可将其与类型的名字进行比较,而不能与 Class
对象作比较。在前面的例子中,你可能觉得写出那一大堆 instanceof 表达式是很乏味的,
的确如此。但是你也没有办法让 instanceof 聪明起来,能够自动地创建一个 Class 对象
的数组,然后将目标对象与这个数组中的对象进行逐一的比较。(稍后你会看到一个替代方
案 )。 其 实 这 并 非 十 分 严 格 的 限 制 , 渐 渐 地 你 就 会 理 解 , 如 果 程 序 中 编 写 了 许 多 的
instanceof 表达式,就说明你的设计可能存在瑕疵。
这个例子当然是刻意制作的,真要跟踪计数,最可能的做法是在每个类里添加一个 static
域,在构造器中逐渐累加此域。如果你掌握了类的源代码,并且能够改变它,你可能就会这
1. 经典的类型转换,如"(Shape)",由 RTTI 确保类型转换的正确性,如果你执行了
一个错误的类型转换,就会抛出一个 ClassCastException 异常。
2. 代表对象类型的 Class 对象。通过查询 Class 对象可以获取运行期所需的信息。
在 C++中,经典的类型转换"(Shape)"并不使用 RTTI。它只是简单地告诉编译器将这个对
象作为新的类型对待。而 Java 要执行类型检查,这通常被称为“类型安全的向下转型(type
safe downcast)”。之所以叫“向下转型”,是由于类层次结构图从来就是这么排列的。如果
将 Circle 类型转换为 Shape 被称作向上转型,那将 Shape 转型为 Circle,就被称为向下转型。
你知道 Circle 肯定是一个 Shape,所以编译器允许自由地做向上转型的赋值操作;然而,你
却不能保证一个 Shape 肯定是 Circle,因此,如果不显式做类型转换,编译器是不会自动做
向下转型操作的。
RTTI 在 Java 中还有第三种形式,就是关键字 instanceof。它返回一个布尔值,告诉我们对象
是不是某个特定类型的实例。你可以用提问的方式使用它,就象这样:
if(x instanceof Dog)
((Dog)x).bark();
在将 x 转型成一个 Dog 前,上面的 if 语句会检查对象 x 是否从属于 Dog 类。进行向下转
型前,如果没有其他信息可以告诉你这个对象是什么类型,那么使用 instanceof 是非常
重要的,否则会得到一个 ClassCastException 异常。
通常,你可能要查找一种类型(比如要找三角形,并填充成紫色),这时你可以轻松的使用
instanceof 来查找。假设你有一个 Pet 类的继承体系:
//: c10:Pet.java
package c10;
public class Pet {} ///:~
//: c10:Dog.java
package c10;
public class Dog extends Pet {} ///:~
//: c10:Pug.java
package c10;
public class Pug extends Dog {} ///:~
//: c10:Cat.java
package c10;
public class Cat extends Pet {} ///:~
//: c10:Rodent.java
package c10;
public class Rodent extends Pet {} ///:~
//: c10:Gerbil.java
package c10;
public class Gerbil extends Rodent {} ///:~
//: c10:Hamster.java
package c10;
public class Hamster extends Rodent {} ///:~
接下来的例子中,我们想跟踪每种特定类型的 Pet 的数量,因此我们需要一个类用来将这个
数量存储为一个 Int 类型,你可以把它看作是一个可修改的 Integer 对象:
//: c10:Counter.java
package c10;
public class Counter {
int i;
public String toString() { return Integer.toString(i); }
} ///:~
然后我们需要一个工具,以同时保存两件东西:一个是 Pet 具体类型的指示器,另一个是当
前 pet 具体类型的对象数量的计数器。也就是说,我们希望能够表达:“这里有多少个 Gerbil
对象”。普通的数组在这里是不行的,因为在数组里我们只能通过索引来查询对象。而这里
我们需要通过具体的 Pet 类型名来查找对象。还要把计数器对象与表示 Pet 具体类型的对象
关联起来。我们将使用一种标准的数据结构,“关联型数组(associative array)”。下面是一
个非常简单的版本:
//: c10:AssociativeArray.java
// Associates keys with values.
package c10;
import com.bruceeckel.simpletest.*;
public class AssociativeArray {
private static Test monitor = new Test();
private Object[][] pairs;
private int index;
public AssociativeArray(int length) {
pairs = new Object[length][2];
}
public void put(Object key, Object value) {
if(index >= pairs.length)
throw new ArrayIndexOutOfBoundsException();
pairs[index++] = new Object[] { key, value };
}
public Object get(Object key) {
for(int i = 0; i < index; i++)
if(key.equals(pairs[i][0]))
return pairs[i][1];
throw new RuntimeException("Failed to find key");
}
public String toString() {
String result = "";
for(int i = 0; i < index; i++) {
result += pairs[i][0] + " : " + pairs[i][1];
if(i < index - 1) result += "\n";
}
return result;
}
public static void main(String[] args) {
AssociativeArray map = new AssociativeArray(6);
map.put("sky", "blue");
map.put("grass", "green");
map.put("ocean", "dancing");
map.put("tree", "tall");
map.put("earth", "brown");
map.put("sun", "warm");
try {
map.put("extra", "object"); // Past the end
} catch(ArrayIndexOutOfBoundsException e) {
System.out.println("Too many objects!");
}
System.out.println(map);
System.out.println(map.get("ocean"));
monitor.expect(new String[] {
"Too many objects!",
"sky : blue",
"grass : green",
"ocean : dancing",
"tree : tall",
"earth : brown",
"sun : warm",
"dancing"
});
}
} ///:~
首 先 你 可 能 会 发 现 , 关 联 型 数 组 看 起 来 像 是 个 通 用 型 的 工 具 , 为 什 么 不 把 它 放 入
com.bruceeckel.tools 包中呢?确实,它是一个很有用的通用型的工具。实际上,
java.util 中包含了好几个关联型数组(也称作映射 map),它们可比我们用的这个数组
功能还要强大得多,并且执行速度更快。第十一章中将用很大的篇幅来讲解关联型数组,它
们相当复杂。所以我们只使用这个简单的版本,同时也使你熟悉关联型数组的用处。
在一个关联型数组中,索引也称作“键(key)”,关联的对象称作“值(value)”。我们通
过把键与值作为“键值对”存入一个二元素数组构成的二维数组中,来将它们关联在一起,
这也就是你在程序中看到的 pairs。这是一个固定长度的数组,所以我们使用 index 以保
证不会越界。当你用 put()方法添加键值对时,就生成一个新的只有两个元素的数组,并
添加在 pairs 中的下一个可用的位置。如果 index 大于或等于 pairs 的长度,就会抛出
异常。
使用 get()方法,你只需将要查找的键传给它,它返回与键相关联的值作为结果,或者在
没有找到相关联的值的情况下抛出异常。get()所使用的定位值的方法可能是你所能想象得
到的最低效的一种:从数组顶端开始,依次调用 equals()来比较键。但现在我们所关心的
是简单性,而不是高效性,而且第十一章中的真正的映射(map)已经解决了执行效率的问
题,所以现在我们不需要为此而担心。
关联型数组中基本的方法就是 put()和 get(),但是为了便于显示,toString()被重载
用来打印键值对。为了表明它是如何工作的,main()先向 AssociativeArray 中加载一
些键值对,然后把映射打印出来,最后还使用了 get()获取一个特定值。
现在所有的工具都备齐了,我们能使用 instanceof 来给 Pet 计数了。
//: c10:PetCount.java
// Using instanceof.
package c10;
import com.bruceeckel.simpletest.*;
import java.util.*;
public class PetCount {
private static Test monitor = new Test();
private static Random rand = new Random();
static String[] typenames = {
"Pet", "Dog", "Pug", "Cat",
"Rodent", "Gerbil", "Hamster",
};
// Exceptions thrown to console:
public static void main(String[] args) {
Object[] pets = new Object[15];
try {
Class[] petTypes = {
Class.forName("c10.Dog"),
Class.forName("c10.Pug"),
Class.forName("c10.Cat"),
Class.forName("c10.Rodent"),
Class.forName("c10.Gerbil"),
Class.forName("c10.Hamster"),
};
for(int i = 0; i < pets.length; i++)
pets[i] = petTypes[rand.nextInt(petTypes.length)]
.newInstance();
} catch(InstantiationException e) {
System.out.println("Cannot instantiate");
System.exit(1);
} catch(IllegalAccessException e) {
System.out.println("Cannot access");
System.exit(1);
} catch(ClassNotFoundException e) {
System.out.println("Cannot find class");
System.exit(1);
}
AssociativeArray map =
new AssociativeArray(typenames.length);
for(int i = 0; i < typenames.length; i++)
map.put(typenames[i], new Counter());
for(int i = 0; i < pets.length; i++) {
Object o = pets[i];
if(o instanceof Pet)
((Counter)map.get("Pet")).i++;
if(o instanceof Dog)
((Counter)map.get("Dog")).i++;
if(o instanceof Pug)
((Counter)map.get("Pug")).i++;
if(o instanceof Cat)
((Counter)map.get("Cat")).i++;
if(o instanceof Rodent)
((Counter)map.get("Rodent")).i++;
if(o instanceof Gerbil)
((Counter)map.get("Gerbil")).i++;
if(o instanceof Hamster)
((Counter)map.get("Hamster")).i++;
}
// List each individual pet:
for(int i = 0; i < pets.length; i++)
System.out.println(pets[i].getClass());
// Show the counts:
System.out.println(map);
monitor.expect(new Object[] {
new TestExpression("%% class c10\\."+
"(Dog|Pug|Cat|Rodent|Gerbil|Hamster)",
pets.length),
new TestExpression(
"%% (Pet|Dog|Pug|Cat|Rodent|Gerbil|Hamster)" +
" : \\d+", typenames.length)
});
}
} ///:~
在 main()中,用 Class.forName()创建了一个名为 petTypes 的 Class 对象的数组。
由于 Pet 对象属于 c10 这个包(package),因此命名的时候要把 package 的名字也包括
进去。
接 下 来 是 填 充 pets 数 组 。 从 petTyeps 中 随 机 选 择 Class 对 象 , 并 通 过
Class.newInstance()调用缺省的(无参数)构造器生成新的对象,加入到 pets 数组
中。
forName()与 newInstance()都可能会抛出异常,你可以在 try 语句块后紧跟着的
catch 子句中看到是如何处理这些异常的。记住,异常的名字对发生的错误是相当有用的
解释(例如 IllegalAccessException 表示违背了 Java 的安全机制)。
在创建了 AssociativeArray 之后,它就被填充满了由 pet 名字和数量组成的键值对。
在这个随机生成的数组中每个 Pet 都是通过使用 instanceof 来检测并计数的。这个数组
和 AssociativeArray 都被打印了出来,这样你就可以比较结果了。
对 instanceof 有比较严格的限制:你只可将其与类型的名字进行比较,而不能与 Class
对象作比较。在前面的例子中,你可能觉得写出那一大堆 instanceof 表达式是很乏味的,
的确如此。但是你也没有办法让 instanceof 聪明起来,能够自动地创建一个 Class 对象
的数组,然后将目标对象与这个数组中的对象进行逐一的比较。(稍后你会看到一个替代方
案 )。 其 实 这 并 非 十 分 严 格 的 限 制 , 渐 渐 地 你 就 会 理 解 , 如 果 程 序 中 编 写 了 许 多 的
instanceof 表达式,就说明你的设计可能存在瑕疵。
这个例子当然是刻意制作的,真要跟踪计数,最可能的做法是在每个类里添加一个 static
域,在构造器中逐渐累加此域。如果你掌握了类的源代码,并且能够改变它,你可能就会这
样做。但实际情况并非总是这样,这时就需要使用 RTTI。