返回介绍

11.1.2 造型前的检查

发布于 2024-10-15 23:56:25 字数 8211 浏览 0 评论 0 收藏 0

迄今为止,我们已知的 RTTI 形式包括:

(1) 经典造型,如"(Shape)",它用 RTTI 确保造型的正确性,并在遇到一个失败的造型后产生一个 ClassCastException 违例。

(2) 代表对象类型的 Class 对象。可查询 Class 对象,获取有用的运行期资料。

在 C++中,经典的"(Shape)"造型并不执行 RTTI。它只是简单地告诉编译器将对象当作新类型处理。而 Java 要执行类型检查,这通常叫作“类型安全”的下溯造型。之所以叫“下溯造型”,是由于类分层结构的历史排列方式造成的。若将一个 Circle(圆)造型到一个 Shape(几何形状),就叫做上溯造型,因为圆只是几何形状的一个子集。反之,若将 Shape 造型至 Circle,就叫做下溯造型。然而,尽管我们明确知道 Circle 也是一个 Shape,所以编译器能够自动上溯造型,但却不能保证一个 Shape 肯定是一个 Circle。因此,编译器不允许自动下溯造型,除非明确指定一次这样的造型。

RTTI 在 Java 中存在三种形式。关键字 instanceof 告诉我们对象是不是一个特定类型的实例(Instance 即“实例”)。它会返回一个布尔值,以便以问题的形式使用,就象下面这样:

if(x instanceof Dog)

((Dog)x).bark();

将 x 造型至一个 Dog 前,上面的 if 语句会检查对象 x 是否从属于 Dog 类。进行造型前,如果没有其他信息可以告诉自己对象的类型,那么 instanceof 的使用是非常重要的——否则会得到一个 ClassCastException 违例。

我们最一般的做法是查找一种类型(比如要变成紫色的三角形),但下面这个程序却演示了如何用 instanceof 标记出所有对象。

//: PetCount.java
// Using instanceof
package c11.petcount;
import java.util.*;

class Pet {}
class Dog extends Pet {}
class Pug extends Dog {}
class Cat extends Pet {}
class Rodent extends Pet {}
class Gerbil extends Rodent {}
class Hamster extends Rodent {}

class Counter { int i; }

public class PetCount {
  static String[] typenames = {
    "Pet", "Dog", "Pug", "Cat",
    "Rodent", "Gerbil", "Hamster",
  };
  public static void main(String[] args) {
    Vector pets = new Vector();
    try {
      Class[] petTypes = {
        Class.forName("c11.petcount.Dog"),
        Class.forName("c11.petcount.Pug"),
        Class.forName("c11.petcount.Cat"),
        Class.forName("c11.petcount.Rodent"),
        Class.forName("c11.petcount.Gerbil"),
        Class.forName("c11.petcount.Hamster"),
      };
      for(int i = 0; i < 15; i++)
        pets.addElement(
          petTypes[
            (int)(Math.random()*petTypes.length)]
            .newInstance());
    } catch(InstantiationException e) {}
      catch(IllegalAccessException e) {}
      catch(ClassNotFoundException e) {}
    Hashtable h = new Hashtable();
    for(int i = 0; i < typenames.length; i++)
      h.put(typenames[i], new Counter());
    for(int i = 0; i < pets.size(); i++) {
      Object o = pets.elementAt(i);
      if(o instanceof Pet)
        ((Counter)h.get("Pet")).i++;
      if(o instanceof Dog)
        ((Counter)h.get("Dog")).i++;
      if(o instanceof Pug)
        ((Counter)h.get("Pug")).i++;
      if(o instanceof Cat)
        ((Counter)h.get("Cat")).i++;
      if(o instanceof Rodent)
        ((Counter)h.get("Rodent")).i++;
      if(o instanceof Gerbil)
        ((Counter)h.get("Gerbil")).i++;
      if(o instanceof Hamster)
        ((Counter)h.get("Hamster")).i++;
    }
    for(int i = 0; i < pets.size(); i++)
      System.out.println(
        pets.elementAt(i).getClass().toString());
    for(int i = 0; i < typenames.length; i++)
      System.out.println(
        typenames[i] + " quantity: " +
        ((Counter)h.get(typenames[i])).i);
  }
} ///:~

在 Java 1.0 中,对 instanceof 有一个比较小的限制:只可将其与一个已命名的类型比较,不能同 Class 对象作对比。在上述例子中,大家可能觉得将所有那些 instanceof 表达式写出来是件很麻烦的事情。实际情况正是这样。但在 Java 1.0 中,没有办法让这一工作自动进行——不能创建 Class 的一个 Vector,再将其与之比较。大家最终会意识到,如编写了数量众多的 instanceof 表达式,整个设计都可能出现问题。

当然,这个例子只是一个构想——最好在每个类型里添加一个 static 数据成员,然后在构建器中令其增值,以便跟踪计数。编写程序时,大家可能想象自己拥有类的源码控制权,能够自由改动它。但由于实际情况并非总是这样,所以 RTTI 显得特别方便。

1. 使用类标记

PetCount.java 示例可用 Java 1.1 的类标记重写一遍。得到的结果显得更加明确易懂:

//: PetCount2.java
// Using Java 1.1 class literals
package c11.petcount2;
import java.util.*;

class Pet {}
class Dog extends Pet {}
class Pug extends Dog {}
class Cat extends Pet {}
class Rodent extends Pet {}
class Gerbil extends Rodent {}
class Hamster extends Rodent {}

class Counter { int i; }

public class PetCount2 {
  public static void main(String[] args) {
    Vector pets = new Vector();
    Class[] petTypes = {
      // Class literals work in Java 1.1+ only:
      Pet.class,
      Dog.class,
      Pug.class,
      Cat.class,
      Rodent.class,
      Gerbil.class,
      Hamster.class,
    };
    try {
      for(int i = 0; i < 15; i++) {
        // Offset by one to eliminate Pet.class:
        int rnd = 1 + (int)(
          Math.random() * (petTypes.length - 1));
        pets.addElement(
          petTypes[rnd].newInstance());
      }
    } catch(InstantiationException e) {}
      catch(IllegalAccessException e) {}
    Hashtable h = new Hashtable();
    for(int i = 0; i < petTypes.length; i++)
      h.put(petTypes[i].toString(),
        new Counter());
    for(int i = 0; i < pets.size(); i++) {
      Object o = pets.elementAt(i);
      if(o instanceof Pet)
        ((Counter)h.get(
          "class c11.petcount2.Pet")).i++;
      if(o instanceof Dog)
        ((Counter)h.get(
          "class c11.petcount2.Dog")).i++;
      if(o instanceof Pug)
        ((Counter)h.get(
          "class c11.petcount2.Pug")).i++;
      if(o instanceof Cat)
        ((Counter)h.get(
          "class c11.petcount2.Cat")).i++;
      if(o instanceof Rodent)
        ((Counter)h.get(
          "class c11.petcount2.Rodent")).i++;
      if(o instanceof Gerbil)
        ((Counter)h.get(
          "class c11.petcount2.Gerbil")).i++;
      if(o instanceof Hamster)
        ((Counter)h.get(
          "class c11.petcount2.Hamster")).i++;
    }
    for(int i = 0; i < pets.size(); i++)
      System.out.println(
        pets.elementAt(i).getClass().toString());
    Enumeration keys = h.keys();
    while(keys.hasMoreElements()) {
      String nm = (String)keys.nextElement();
      Counter cnt = (Counter)h.get(nm);
      System.out.println(
        nm.substring(nm.lastIndexOf('.') + 1) + 
        " quantity: " + cnt.i);
    }
  }
} ///:~

在这里,typenames(类型名)数组已被删除,改为从 Class 对象里获取类型名称。注意为此而额外做的工作:例如,类名不是 Getbil,而是 c11.petcount2.Getbil,其中已包含了包的名字。也要注意系统是能够区分类和接口的。

也可以看到,petTypes 的创建模块不需要用一个 try 块包围起来,因为它会在编译期得到检查,不会象 Class.forName() 那样“掷”出任何违例。

Pet 动态创建好以后,可以看到随机数字已得到了限制,位于 1 和 petTypes.length 之间,而且不包括零。那是由于零代表的是 Pet.class,而且一个普通的 Pet 对象可能不会有人感兴趣。然而,由于 Pet.class 是 petTypes 的一部分,所以所有 Pet(宠物)都会算入计数中。

2. 动态的 instanceof

Java 1.1 为 Class 类添加了 isInstance 方法。利用它可以动态调用 instanceof 运算符。而在 Java 1.0 中,只能静态地调用它(就象前面指出的那样)。因此,所有那些烦人的 instanceof 语句都可以从 PetCount 例子中删去了。如下所示:

//: PetCount3.java
// Using Java 1.1 isInstance()
package c11.petcount3;
import java.util.*;

class Pet {}
class Dog extends Pet {}
class Pug extends Dog {}
class Cat extends Pet {}
class Rodent extends Pet {}
class Gerbil extends Rodent {}
class Hamster extends Rodent {}

class Counter { int i; }

public class PetCount3 {
  public static void main(String[] args) {
    Vector pets = new Vector();
    Class[] petTypes = {
      Pet.class,
      Dog.class,
      Pug.class,
      Cat.class,
      Rodent.class,
      Gerbil.class,
      Hamster.class,
    };
    try {
      for(int i = 0; i < 15; i++) {
        // Offset by one to eliminate Pet.class:
        int rnd = 1 + (int)(
          Math.random() * (petTypes.length - 1));
        pets.addElement(
          petTypes[rnd].newInstance());
      }
    } catch(InstantiationException e) {}
      catch(IllegalAccessException e) {}
    Hashtable h = new Hashtable();
    for(int i = 0; i < petTypes.length; i++)
      h.put(petTypes[i].toString(),
        new Counter());
    for(int i = 0; i < pets.size(); i++) {
      Object o = pets.elementAt(i);
      // Using isInstance to eliminate individual
      // instanceof expressions:
      for (int j = 0; j < petTypes.length; ++j)
        if (petTypes[j].isInstance(o)) {
          String key = petTypes[j].toString();
          ((Counter)h.get(key)).i++;
        }
    }
    for(int i = 0; i < pets.size(); i++)
      System.out.println(
        pets.elementAt(i).getClass().toString());
    Enumeration keys = h.keys();
    while(keys.hasMoreElements()) {
      String nm = (String)keys.nextElement();
      Counter cnt = (Counter)h.get(nm);
      System.out.println(
        nm.substring(nm.lastIndexOf('.') + 1) + 
        " quantity: " + cnt.i);
    }
  }
} ///:~

可以看到,Java 1.1 的 isInstance() 方法已取消了对 instanceof 表达式的需要。此外,这也意味着一旦要求添加新类型宠物,只需简单地改变 petTypes 数组即可;毋需改动程序剩余的部分(但在使用 instanceof 时却是必需的)。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文