运行期类型鉴定

why RTTI(Run Time Type Indentify)?

在这里插入图片描述
这是一个典型的类结构示意图,基础类位于顶部,衍生类向下延展。面向对象编程的基本目标是用大量代码控制基础类型(这里是 Shape)的句柄,所以假如决定添加一个新类(比如 Rhomboid,从Shape 衍生),从而对程序进行扩展,那么不会影响到原来的代码。在这个例子中,Shape 接口中的动态绑定方法是draw(),所以客户程序员要做的是通过一个普通Shape 句柄调用draw()。draw()在所有衍生类里都会被覆盖。而且由于它是一个动态绑定方法,所以即使通过一个普通的Shape 句柄调用它,也有表现出正确的行为。这正是多形性的作用。所以,我们一般创建一个特定的对象(Circle,Square,或者 Triangle),把它上溯造型到一个Shape(忽略对象的特殊类型),以后便在程序的剩余部分使用匿名 Shape 句柄。作为对多形性和上溯造型的一个简要回顾,可以象下面这样为上述例子编码

package thinking.in.java.chapter11;

//: Shapes.java
import java.util.*;
interface Shape {
    void draw();
}
class Circle implements Shape {
    public void draw() {
        System.out.println("Circle.draw()");
    }
}
class Square implements Shape {

    public void draw() {
        System.out.println("Square.draw()");
    }
}
class Triangle implements Shape {
    public void draw() {
        System.out.println("Triangle.draw()");
    }
}
public class Shapes {
    public static void main(String[] args) {
        Vector s = new Vector();
        s.addElement(new Circle());
        s.addElement(new Square());
        s.addElement(new Triangle());
        Enumeration e = s.elements();
        while(e.hasMoreElements())
            ((Shape)e.nextElement()).draw();
    }
} ///:~

在这里插入图片描述
基础类可编码成一个interface(接口)、一个abstract(抽象)类或者一个普通类。由于 Shape 没有真正的成员(亦即有定义的成员),而且并不在意我们创建了一个纯粹的 Shape 对象,所以最适合和最灵活的表达方式便是用一个接口。而且由于不必设置所有那些abstract 关键字,所以整个代码也显得更为清爽。每个衍生类都覆盖了基础类draw 方法,所以具有不同的行为。在main()中创建了特定类型的Shape,然后将其添加到一个Vector。这里正是上溯造型发生的地方,因为Vector 只容纳了对象。由于Java 中的所有东西(除基本数据类型外)都是对象,所以Vector 也能容纳 Shape 对象。但在上溯造型至 Object 的过程中,任何特殊的信息都会丢失,其中甚至包括对象是几何形状这一事实。对 Vect or 来说,它们只是Object。 用nextElement()将一个元素从 Vector 提取出来的时候,情况变得稍微有些复杂。由于 Vector 只容纳Object,所以 nextElement()会自然地产生一个 Object 句柄。但我们知道它实际是个 Shape 句柄,而且希望将Shape 消息发给那个对象。所以需要用传统的"(Shape)"方式造型成一个Shape。这是 RTTI 最基本的形式,因为在 Java 中,所有造型都会在运行期间得到检查,以确保其正确性。那正是RTTI 的意义所在:在运行期,对象的类型会得到鉴定。

在目前这种情况下,RTTI 造型只实现了一部分:Object 造型成 Shape,而不是造型成Circle,Square 或者Triangle。那是由于我们目前能够肯定的唯一事实就是 Vector 里充斥着几何形状,而不知它们的具体类别。在编译期间,我们肯定的依据是我们自己的规则;而在编译期间,却是通过造型来肯定这一点。现在的局面会由多形性控制,而且会为Shape 调用适当的方法,以便判断句柄到底是提供Circle,Square,还是提供给 Triangle。而且在一般情况下,必须保证采用多形性方案。因为我们希望自己的代码尽可能少知道一些与对象的具体类型有关的情况,只将注意力放在某一类对象(这里是Shape)的常规信息上。只有这样,我们的代码才更易实现、理解以及修改。所以说多形性是面向对象程序设计的一个常规目标。然而,若碰到一个特殊的程序设计问题,只有在知道常规句柄的确切类型后,才能最容易地解决这个问题,这个时候又该怎么办呢?举个例子来说,我们有时候想让自己的用户将某一具体类型的几何形状(如三角形)全都变成紫色,以便突出显示它们,并快速找出这一类型的所有形状。此时便要用到RTTI 技术,用它查询某个 Shape 句柄引用的准确类型是什么。

Class对象

为理解 RTTI 在 Java 里如何工作,首先必须了解类型信息在运行期是如何表示的。这时要用到一个名为“Class 对象”的特殊形式的对象,其中包含了与类有关的信息(有时也把它叫作“元类”)。事实上,我们要用 Class 对象创建属于某个类的全部“常规”或“普通”对象。对于作为程序一部分的每个类,它们都有一个 Class 对象。换言之,每次写一个新类时,同时也会创建一个Class 对象(更恰当地说,是保存在一个完全同名的.class 文件中)。在运行期,一旦我们想生成那个类的一个对象,用于执行程序的 Java 虚拟机(JVM)首先就会检查那个类型的Class 对象是否已经载入。若尚未载入,JVM 就会查找同名的.class 文件,并将其载入。所以Java 程序启动时并不是完全载入的,这一点与许多传统语言都不同。一旦那个类型的Class 对象进入内存,就用它创建那一类型的所有对象。若这种说法多少让你产生了一点儿迷惑,或者并没有真正理解它,下面这个示程序或许能提供进一步的帮助:

package thinking.in.java.chapter11;

//: SweetShop.java
// Examination of the way the class loader works
class Candy {
    static {
        System.out.println("Loading Candy");
    }
}
class Gum {
    static {
        System.out.println("Loading Gum");
    }
}
class Cookie {
    static {
        System.out.println("Loading Cookie");
    }
}
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("thinking.in.java.chapter11.Gum");
        } catch(ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("After Class.forName(\"Gum\")");
        new Cookie();
        System.out.println("After creating Cookie");
    }
} ///:~

在这里插入图片描述
可以看到,每个Class 只有在它需要的时候才会载入,而 static 初始化工作是在类载入时执行的。

看来JVM 通过检查main()中的代码,已经预测到了对Candy 和Cookie 的需要,但却看不到Gum,因为它是通过对forName()的一个调用创建的,而不是通过更典型的 new 调用。尽管这个JVM 也达到了我们希望的效果,因为确实会在我们需要之前载入那些类,但却不能肯定这儿展示的行为百分之百正确。

1.类标记

在Java 1.1 中,可以采用第二种方式来产生 Class 对象的句柄:使用“类标记”。对上述程序来说,看起来就象下面这样:Gum.class;

这样做不仅更加简单,而且更安全,因为它会在编译期间得到检查。由于它取消了对方法调用的需要,所以执行的效率也会更高。

类标记不仅可以应用于普通类,也可以应用于接口、数组以及基本数据类型。除此以外,针对每种基本数据类型的封装器类,它还存在一个名为TYPE 的标准字段。TYPE 字段的作用是为相关的基本数据类型产生 Class对象的一个句柄,如下所示:
在这里插入图片描述

11.1.2 造型前的检查

迄今为止,我们已知的 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 标记出所有对象

package thinking.in.java.chapter11;

//: PetCount.java
// Using instanceof
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("thinking.in.java.chapter11.Dog"),
                    Class.forName("thinking.in.java.chapter11.Pug"),
                    Class.forName("thinking.in.java.chapter11.Cat"),
                    Class.forName("thinking.in.java.chapter11.Rodent"),
                    Class.forName("thinking.in.java.chapter11.Gerbil"),
                    Class.forName("thinking.in.java.chapter11.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);
    }
} ///:~

在这里插入图片描述

1.使用类标记

package thinking.in.java.chapter11.petCount2;

/**
 * 1. 使用类标记
 * PetCount.java 示例可用Java 1.1 的类标记重写一遍。得到的结果显得更加明确易懂:
 */
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 thinking.in.java.chapter11.petCount2.Pet")).i++;
            if(o instanceof Dog)
                ((Counter)h.get("class thinking.in.java.chapter11.petCount2.Dog")).i++;
            if(o instanceof Pug)
                ((Counter)h.get("class thinking.in.java.chapter11.petCount2.Pug")).i++;
            if(o instanceof Cat)
                ((Counter)h.get("class thinking.in.java.chapter11.petCount2.Cat")).i++;
            if(o instanceof Rodent)
                ((Counter)h.get("class thinking.in.java.chapter11.petCount2.Rodent")).i++;
            if(o instanceof Gerbil)
                ((Counter)h.get("class thinking.in.java.chapter11.petCount2.Gerbil")).i++;
            if(o instanceof Hamster)
                ((Counter)h.get("class thinking.in.java.chapter11.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);
        }
    }
} ///:~

在这里插入图片描述

package thinking.in.java.chapter11.petCount3;

/**
 * 2. 动态的 instanceof
 * Java 1.1 为Class 类添加了 isInstance 方法。利用它可以动态调用instanceof 运算符。而在Java 1.0
 * 中,只能静态地调用它(就象前面指出的那样)。因此,所有那些烦人的instanceof 语句都可以从
 * PetCount 例子中删去了。如下所示:
 * //:
 */
//: PetCount3.java
// Using Java 1.1 isInstance()
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);
        }
    }
}

在这里插入图片描述

11.2 RTTI 语法

Java 用Class 对象实现自己的RTTI 功能——即便我们要做的只是象造型那样的一些工作。Class 类也提供了其他大量方式,以方便我们使用RTTI。

首先必须获得指向适当 Class 对象的的一个句柄。就象前例演示的那样,一个办法是用一个字串以及Class.forName()方法。这是非常方便的,因为不需要那种类型的一个对象来获取 Class 句柄。

然而,对于自己感兴趣的类型,如果已有了它的一个对象,那么为了取得Class 句柄,可调用属于 Object 根类一部分的一个方法:getClass()。它的作用是返回一个特定的 Class 句柄,用来表示对象的实际类型。Class 提供了几个有趣且较为有用的方法,从下例即可看出:

package thinking.in.java.chapter11;

//: ToyTest.java
// Testing class Class
interface HasBatteries {}
interface Waterproof {}
interface ShootsThings {}
class Toy {
    // Comment out the following default
    // constructor to see
    // NoSuchMethodError from (*1*)
    Toy() {}
    Toy(int i) {}
}
class FancyToy extends Toy
        implements HasBatteries,
        Waterproof, ShootsThings {
    FancyToy() { super(1); }
}
public class ToyTest {
    public static void main(String[] args) {
        Class c = null;
        try {
            c = Class.forName("thinking.in.java.chapter11.FancyToy");
        } catch(ClassNotFoundException e) {}
        printInfo(c);
        Class[] faces = c.getInterfaces();
        for(int i = 0; i < faces.length; i++)
            printInfo(faces[i]);
        Class cy = c.getSuperclass();
        Object o = null;
        try {
            // Requires default constructor:
            o = cy.newInstance(); // (*1*)
        } catch(InstantiationException e) {}
        catch(IllegalAccessException e) {}
        printInfo(o.getClass());
    }
    static void printInfo(Class cc) {
        System.out.println("Class name: " + cc.getName() + " is interface? [" + cc.isInterface() + "]");
    }
} ///:~

在这里插入图片描述
从中可以看出,class FancyToy 相当复杂,因为它从Toy 中继承,并实现了 HasBatteries,Waterproof 以 及ShootsThings 的接口。在 main()中创建了一个 Class 句柄,并用位于相应 try 块内的 forName()初始化成
FancyToy。

Class.getInterfaces 方法会返回 Class 对象的一个数组,用于表示包含在 Class 对象内的接口。

若有一个Class 对象,也可以用getSuperclass()查询该对象的直接基础类是什么。当然,这种做会返回一个Class 句柄,可用它作进一步的查询。这意味着在运行期的时候,完全有机会调查到对象的完整层次结构。

若从表面看,Class 的 newInstance()方法似乎是克隆(clone())一个对象的另一种手段。但两者是有区别的。利用newInstance(),我们可在没有现成对象供“克隆”的情况下新建一个对象。就象上面的程序演示的那样,当时没有 Toy 对象,只有 cy——即 y 的 Class 对象的一个句柄。利用它可以实现“虚拟构建器”。换言之,我们表达:“尽管我不知道你的准确类型是什么,但请你无论如何都正确地创建自己。”在上述例子中,cy 只是一个Class 句柄,编译期间并不知道进一步的类型信息。一旦新建了一个实例后,可以得到Object 句柄。但那个句柄指向一个Toy 对象。

当然,如果要将除Object 能够接收的其他任何消息发出去,首先必须进行一些调查研究,再进行造型。除此以外,用 newInstance()创建的类必须有一个默认构建器。没有办法用 newInstance()创建拥有非默认构建器的对象,所以在Java 1.0 中可能存在一些限制。然而,Java 1.1 的“反射”API(下一节讨论)却允许我们动态地使用类里的任何构建器。程序中的最后一个方法是printInfo(),它取得一个Class 句柄,通过 getName()获得它的名字,并用interface()调查它是不是一个接口。

反射

package thinking.in.java.chapter11;

//: ShowMethods.java
// Using Java 1.1 reflection to show all the
// methods of a class, even if the methods are
// defined in the base class.
import java.lang.reflect.*;
public class ShowMethods {
    static final String usage =
            "usage: \n" +
                    "ShowMethods qualified.class.name\n" +
                    "To show all methods in class or: \n" +
                    "ShowMethods qualified.class.name word\n" +
                    "To search for methods involving 'word'";
    public static void main(String[] args) {
        if(args.length < 1) {
            System.out.println(usage);
            System.exit(0);
        }
        try {Class c = Class.forName(args[0]);
            Method[] m = c.getMethods();
            Constructor[] ctor = c.getConstructors();
            if(args.length == 1) {
                for (int i = 0; i < m.length; i++)
                    System.out.println(m[i].toString());
                for (int i = 0; i < ctor.length; i++)
                    System.out.println(ctor[i].toString());
            }
            else {
                for (int i = 0; i < m.length; i++)
                    if(m[i].toString()
                            .indexOf(args[1])!= -1)
                        System.out.println(m[i].toString());
                for (int i = 0; i < ctor.length; i++)
                    if(ctor[i].toString()
                            .indexOf(args[1])!= -1)
                        System.out.println(ctor[i].toString());
            }
        } catch (ClassNotFoundException e) {
            System.out.println("No such class: " + e);
        }
    }
} ///:~

在这里插入图片描述

package thinking.in.java.chapter11;

// ShowMethods with the qualifiers stripped
// to make the results easier to read
import java.lang.reflect.*;
import java.io.*;
public class ShowMethodsClean {
    static final String usage =
            "usage: \n" +
                    "ShowMethodsClean qualified.class.name\n" +
                    "To show all methods in class or: \n" +
                    "ShowMethodsClean qualif.class.name word\n" +
                    "To search for methods involving 'word'";
    public static void main(String[] args) {
        if(args.length < 1) {
            System.out.println(usage);
            System.exit(0);
        }
        try {
            Class c = Class.forName(args[0]);
            Method[] m = c.getMethods();
            Constructor[] ctor = c.getConstructors();
            // Convert to an array of cleaned Strings:
            String[] n =
                    new String[m.length + ctor.length];
            for(int i = 0; i < m.length; i++) {
                String s = m[i].toString();
                n[i] = StripQualifiers.strip(s);
            }
            for(int i = 0; i < ctor.length; i++) {
                String s = ctor[i].toString();
                n[i + m.length] =
                        StripQualifiers.strip(s);
            }
            if(args.length == 1)
                for (int i = 0; i < n.length; i++)
                    System.out.println(n[i]);
            else
                for (int i = 0; i < n.length; i++)
                    if(n[i].indexOf(args[1])!= -1)
                        System.out.println(n[i]);
        } catch (ClassNotFoundException e) {
            System.out.println("No such class: " + e);
        }
    }
}
class StripQualifiers {
    private StreamTokenizer st;
    public StripQualifiers(String qualified) {
        st = new StreamTokenizer(
                new StringReader(qualified));
        st.ordinaryChar(' '); // Keep the spaces
    }
    public String getNext() {
        String s = null;
        try {
            if(st.nextToken() !=
                    StreamTokenizer.TT_EOF) {
                switch(st.ttype) {
                    case StreamTokenizer.TT_EOL:
                        s = null;
                        break;
                    case StreamTokenizer.TT_NUMBER:
                        s = Double.toString(st.nval);
                        break;
                    case StreamTokenizer.TT_WORD:
                        s = new String(st.sval);
                        break;
                    default: // single character in ttype
                        s = String.valueOf((char)st.ttype);
                }
            }
        } catch(IOException e) {
            System.out.println(e);
        }
        return s;
    }
    public static String strip(String qualified) {
        StripQualifiers sq =
                new StripQualifiers(qualified);
        String s = "", si;
        while((si = sq.getNext()) != null) {
            int lastDot = si.lastIndexOf('.');
            if(lastDot != -1)
                si = si.substring(lastDot + 1);
            s += si;
        }
        return s;
    }
} ///:~

在这里插入图片描述

11.4 总结

利用RTTI 可根据一个匿名的基础类句柄调查出类型信息。但正是由于这个原因,新手们极易误用它,因为有些时候多形性方法便足够了。对那些以前习惯程序化编程的人来说,极易将他们的程序组织成一系列switch语句。他们可能用 RTTI 做到这一点,从而在代码开发和维护中损失多形性技术的重要价值。Java 的要求是
让我们尽可能地采用多形性,只有在极特别的情况下才使用RTTI。

但为了利用多形性,要求我们拥有对基础类定义的控制权,因为有些时候在程序范围之内,可能发现基础类并未包括我们想要的方法。若基础类来自一个库,或者由别的什么东西控制着,RTTI 便是一种很好的解决方案:可继承一个新类型,然后添加自己的额外方法。在代码的其他地方,可以侦测自己的特定类型,并调用那个特殊的方法。这样做不会破坏多形性以及程序的扩展能力,因为新类型的添加不要求查找程序中的switch 语句。但在需要新特性的主体中添加新代码时,就必须用 RTTI 侦测自己特定的类型。

从某个特定类的利益的角度出发,在基础类里加入一个特性后,可能意味着从那个基础类衍生的其他所有类都必须获得一些无意义的“鸡肋”。这使得接口变得含义模糊。若有人从那个基础类继承,且必须覆盖抽象方法,这一现象便会使他们陷入困扰。比如现在用一个类结构来表示乐器(Instrument)。假定我们想清洁
管弦乐队中所有适当乐器的通气音栓(Spit Valve),此时的一个办法是在基础类Instrument 中置入一个ClearSpitValve()方法。但这样做会造成一个误区,因为它暗示着打击乐器和电子乐器中也有音栓。

针对这种情况,RTTI 提供了一个更合理的解决方案,可将方法置入特定的类中(此时是Wind,即“通气口”)——这样做是可行的。但事实上一种更合理的方案是将 prepareInstrument()置入基础类中。初学者刚开始时往往看不到这一点,一般会认定自己必须使用RTTI。

最后,RTTI 有时能解决效率问题。若代码大量运用了多形性,但其中的一个对象在执行效率上很有问题,便可用RTTI 找出那个类型,然后写一段适当的代码,改进其效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值