🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 16.3 模拟垃圾回收站 这个问题的本质是若将垃圾丢进单个垃圾筒,事实上是未经分类的。但在以后,某些特殊的信息必须恢复,以便对垃圾正确地归类。在最开始的解决方案中,RTTI扮演了关键的角色(详见第11章)。 这并不是一种普通的设计,因为它增加了一个新的限制。正是这个限制使问题变得非常有趣——它更象我们在工作中碰到的那些非常麻烦的问题。这个额外的限制是:垃圾抵达垃圾回收站时,它们全都是混合在一起的。程序必须为那些垃圾的分类定出一个模型。这正是RTTI发挥作用的地方:我们有大量不知名的垃圾,程序将正确判断出它们所属的类型。 ``` //: RecycleA.java // Recycling with RTTI package c16.recyclea; import java.util.*; import java.io.*; abstract class Trash { private double weight; Trash(double wt) { weight = wt; } abstract double value(); double weight() { return weight; } // Sums the value of Trash in a bin: static void sumValue(Vector bin) { Enumeration e = bin.elements(); double val = 0.0f; while(e.hasMoreElements()) { // One kind of RTTI: // A dynamically-checked cast Trash t = (Trash)e.nextElement(); // Polymorphism in action: val += t.weight() * t.value(); System.out.println( "weight of " + // Using RTTI to get type // information about the class: t.getClass().getName() + " = " + t.weight()); } System.out.println("Total value = " + val); } } class Aluminum extends Trash { static double val = 1.67f; Aluminum(double wt) { super(wt); } double value() { return val; } static void value(double newval) { val = newval; } } class Paper extends Trash { static double val = 0.10f; Paper(double wt) { super(wt); } double value() { return val; } static void value(double newval) { val = newval; } } class Glass extends Trash { static double val = 0.23f; Glass(double wt) { super(wt); } double value() { return val; } static void value(double newval) { val = newval; } } public class RecycleA { public static void main(String[] args) { Vector bin = new Vector(); // Fill up the Trash bin: for(int i = 0; i < 30; i++) switch((int)(Math.random() * 3)) { case 0 : bin.addElement(new Aluminum(Math.random() * 100)); break; case 1 : bin.addElement(new Paper(Math.random() * 100)); break; case 2 : bin.addElement(new Glass(Math.random() * 100)); } Vector glassBin = new Vector(), paperBin = new Vector(), alBin = new Vector(); Enumeration sorter = bin.elements(); // Sort the Trash: while(sorter.hasMoreElements()) { Object t = sorter.nextElement(); // RTTI to show class membership: if(t instanceof Aluminum) alBin.addElement(t); if(t instanceof Paper) paperBin.addElement(t); if(t instanceof Glass) glassBin.addElement(t); } Trash.sumValue(alBin); Trash.sumValue(paperBin); Trash.sumValue(glassBin); Trash.sumValue(bin); } } ///:~ ``` 要注意的第一个地方是`package`语句: ``` package c16.recyclea; ``` 这意味着在本书采用的源码目录中,这个文件会被置入从`c16`(代表第16章的程序)分支出来的`recyclea`子目录中。第17章的解包工具会负责将其置入正确的子目录。之所以要这样做,是因为本章会多次改写这个特定的例子;它的每个版本都会置入自己的“包”(`package`)内,避免类名的冲突。 其中创建了几个`Vector`对象,用于容纳`Trash`引用。当然,`Vector`实际容纳的是`Object`(对象),所以它们最终能够容纳任何东西。之所以要它们容纳`Trash`(或者从`Trash`派生出来的其他东西),唯一的理由是我们需要谨慎地避免放入除`Trash`以外的其他任何东西。如果真的把某些“错误”的东西置入`Vector`,那么不会在编译期得到出错或警告提示——只能通过运行期的一个异常知道自己已经犯了错误。 `Trash`引用加入后,它们会丢失自己的特定标识信息,只会成为简单的`Object`引用(向上转换)。然而,由于存在多态性的因素,所以在我们通过`Enumeration sorter`调用动态绑定方法时,一旦结果`Object`已经转换回`Trash`,仍然会发生正确的行为。`sumValue()`也用一个`Enumeration`对`Vector`中的每个对象进行操作。 表面上持,先把`Trash`的类型向上转换到一个集合容纳基类型的引用,再回过头重新向下转换,这似乎是一种非常愚蠢的做法。为什么不只是一开始就将垃圾置入适当的容器里呢?(事实上,这正是拨开“回收”一团迷雾的关键)。在这个程序中,我们很容易就可以换成这种做法,但在某些情况下,系统的结构及灵活性都能从向下转换中得到极大的好处。 该程序已满足了设计的初衷:它能够正常工作!只要这是个一次性的方案,就会显得非常出色。但是,真正有用的程序应该能够在任何时候解决问题。所以必须问自己这样一个问题:“如果情况发生了变化,它还能工作吗?”举个例子来说,厚纸板现在是一种非常有价值的可回收物品,那么如何把它集成到系统中呢(特别是程序很大很复杂的时候)?由于前面在`switch`语句中的类型检查编码可能散布于整个程序,所以每次加入一种新类型时,都必须找到所有那些编码。若不慎遗漏一个,编译器除了指出存在一个错误之外,不能再提供任何有价值的帮助。 RTTI在这里使用不当的关键是“每种类型都进行了测试”。如果由于类型的子集需要特殊的对待,所以只寻找那个子集,那么情况就会变得好一些。但假如在一个`switch`语句中查找每一种类型,那么很可能错过一个重点,使最终的代码很难维护。在下一节中,大家会学习如何逐步对这个程序进行改进,使其显得越来越灵活。这是在程序设计中一种非常有意义的例子。