Polymorphism【2】

Constructors and polymorphism

As usual, constructors are different from other kinds of methods. This is also true when polymorphism is involved. Even though constructors are not polymorphic (they’re actually static methods, but the static declaration is implicit), it’s important to understand the way constructors work in complex hierarchies and with polymorphism. This understanding will help you avoid unpleasant entanglements.

一般来讲,构造函数是与其它方法存在差别的,当涉及到多态的时候也不例外。尽管构造方法不是多态的(它们是static方法,尽管关键词并没有声明),关于理解构造方法在复杂的继承类以及多态中是如何工作的是很重要的。理解了这些以后可以帮助你避免很多不愉快的纠缠。

Order of constructor calls

The order of constructor calls was briefly discussed in Chapter 4 and again in Chapter 6, but that was before polymorphism was introduced.

关于构造方法的调用顺序在第四章的时候曾经简要的提到,第六章也再次提及,但是这些都是在多态机制没有产生之前提到的。

A constructor for the base class is always called during the construction process for a derived class, chaining up the inheritance hierarchy so that a constructor for every base class is called. This makes sense because the constructor has a special job: to see that the object is built properly. A derived class has access to its own members only, and not to those of the base class (whose members are typically private). Only the base-class constructor has the proper knowledge and access to initialize its own elements. Therefore, it’s essential that all constructors get called, otherwise the entire object wouldn’t be constructed. That’s why the compiler enforces a constructor call for every portion of a derived class. It will silently call the default constructor if you don’t explicitly call a base-class constructor in the derived-class constructor body. If there is no default constructor, the compiler will complain. (In the case where a class has no constructors, the compiler will automatically synthesize a default constructor.)

一个基类的构造方法在它的派生类的对象创建过程中是会被调用的,根据继承关系图一层层追溯,所有基类的构造方法都会被调用的。造成这种情况是因为构造方法有特殊的功能:验证对象是否被完全的创建了。派生类只能访问自己的成员,并不能访问基类的诸如private访问级别的成员。只有基类的构造方法拥有正确的内容并且访问和初始化这些元素。因此,所有的构造方法被调用是必然的,否则对象将不能被创建。这就是为什么编译器要调用所有的基类的构造方法了。这样及时你不声明调用基类的构造方法它也会调用基类的默认的构造方法,但是如果那里没有默认的构造方法,编译器将出错。(如果某个类没有构造方法那么编译器将自动为你做一个构造方法)。

Let’s take a look at an example that shows the effects of composition, inheritance, and polymorphism on the order of construction:

让我们看一个例子,给我们展示了合成,继承以及多态在调用顺序上对程序的影响。

package c07;

import com.bruceeckel.simpletest.*;

 

class Meal {

  Meal() { System.out.println("Meal()"); }

}

 

class Bread {

  Bread() { System.out.println("Bread()"); }

}

 

class Cheese {

  Cheese() { System.out.println("Cheese()"); }

}

 

class Lettuce {

  Lettuce() { System.out.println("Lettuce()"); }

}

 

class Lunch extends Meal {

  Lunch() { System.out.println("Lunch()"); }

}

 

class PortableLunch extends Lunch {

  PortableLunch() { System.out.println("PortableLunch()");}

}

 

public class Sandwich extends PortableLunch {

  private static Test monitor = new Test();

  private Bread b = new Bread();

  private Cheese c = new Cheese();

  private Lettuce l = new Lettuce();

  public Sandwich() {

    System.out.println("Sandwich()");

  }

  public static void main(String[] args) {

    new Sandwich();

    monitor.expect(new String[] {

      "Meal()",

      "Lunch()",

      "PortableLunch()",

      "Bread()",

      "Cheese()",

      "Lettuce()",

      "Sandwich()"

    });

  }

}

This example creates a complex class out of other classes, and each class has a constructor that announces itself. The important class is Sandwich, which reflects three levels of inheritance (four, if you count the implicit inheritance from Object) and three member objects. You can see the output when a Sandwich object is created in main( ). This means that the order of constructor calls for a complex object is as follows:

  1. The base-class constructor is called. This step is repeated recursively such that the root of the hierarchy is constructed first, followed by the next-derived class, etc., until the most-derived class is reached.  
  2. Member initializers are called in the order of declaration.  
  3. The body of the derived-class constructor is called.

这个例子使用其它的类创建了一个组合的类,并且每个类都有构造方法来描述自己。这里最重要的类是sandwich,这个类用到了三层继承(如果考虑暗含的从Object继承的话就是四层)和三个对象。你可以看到在Main方法中创建sandwich对象的时候的输出,这就表明了一个组合的类的对象创建的时候构造方法的调用是如下这样的:

1、      调用基类的构造方法。这个步骤一直循环递归直到继承的根对象被创建之后,首先第一层继承类的对象被创建,知道最后一层继承类的对象被创建;

2、      成员的调用顺序和成员的成名顺序相同;

3、      执行继承类的构造方法的正文;

The order of the constructor calls is important. When you inherit, you know all about the base class and can access any public and protected members of the base class. This means that you must be able to assume that all the members of the base class are valid when you’re in the derived class. In a normal method, construction has already taken place, so all the members of all parts of the object have been built. Inside the constructor, however, you must be able to assume that all members that you use have been built. The only way to guarantee this is for the base-class constructor to be called first. Then when you’re in the derived-class constructor, all the members you can access in the base class have been initialized. Knowing that all members are valid inside the constructor is also the reason that, whenever possible, you should initialize all member objects (that is, objects placed in the class using composition) at their point of definition in the class (e.g., b, c, and l in the preceding example). If you follow this practice, you will help ensure that all base class members and member objects of the current object have been initialized. Unfortunately, this doesn’t handle every case, as you will see in the next section.

关于构造方法的调用顺序是很重要的。当你采用继承技术的时候你知道所有基类,并且可以访问所有访问基类中所有的publicprotected的成员。但是这以为这你必须要保证基类中所有的成员在继承类中都是有效的。对于普通的过程来讲,构造方法已经执行结束后,这个类的所有成员也就被创建了。但是在构造方法中,你必须确保你所使用的所有成员都已经被创建。而确保这样的只能是所有的基类构造方法已经被首先调用了,这样派生类的构造方法执行时,基类的构造方法已经被调用而且数据成员已经创建了,可以提供给你访问。知道了哪些成员在构造方法中已经是创建好了你也就知道了何时可用在定义对象的时候对对象进行初始化,如果你这么作的话那么就可以保证所有的基类成员以及基类对象都被初始化了,但是这并不能解决所有的情况,下面的章节中我们会讲到。

Inheritance and cleanup

When using composition and inheritance to create a new class, most of the time you won’t have to worry about cleaning up; subobjects can usually be left to the garbage collector. If you do have cleanup issues, you must be diligent and create a dispose( ) method (the name I have chosen to use here; you may come up with something better) for your new class. And with inheritance, you must override dispose( ) in the derived class if you have any special cleanup that must happen as part of garbage collection. When you override dispose( ) in an inherited class, it’s important to remember to call the base-class version of dispose( ), since otherwise the base-class cleanup will not happen. The following example demonstrates this:

当你使用继承和组合来创建新的类的时候,大部分时候你不必担心清理;派生类对象通常都由垃圾回收器去处理。如果你确实需要清理的话,你必须要为你的新类创建一个dispose()方法(我在这里选择了这个名字,你可以选择更好的名字),如果使用了继承的话,如果你希望在执行垃圾回收清理的时候作一些其它的事情,那么你必需要要在派生类中覆写这个方法,当你在派生类中覆写这个方法的时候,你需要记住需要调用基类的dispose()方法,这是非常重要的,否则基类的垃圾回收清理工作将不被执行,下面的例子论证了这点:

import com.bruceeckel.simpletest.*;

 

class Characteristic {

  private String s;

  Characteristic(String s) {

    this.s = s;

    System.out.println("Creating Characteristic " + s);

  }

  protected void dispose() {

    System.out.println("finalizing Characteristic " + s);

  }

}

 

class Description {

  private String s;

  Description(String s) {

    this.s = s;

    System.out.println("Creating Description " + s);

  }

  protected void dispose() {

    System.out.println("finalizing Description " + s);

  }

}

 

class LivingCreature {

  private Characteristic p = new Characteristic("is alive");

  private Description t =

    new Description("Basic Living Creature");

  LivingCreature() {

    System.out.println("LivingCreature()");

  }

  protected void dispose() {

    System.out.println("LivingCreature dispose");

    t.dispose();

    p.dispose();

  }

}

 

class Animal extends LivingCreature {

  private Characteristic p= new Characteristic("has heart");

  private Description t =

    new Description("Animal not Vegetable");

  Animal() {

    System.out.println("Animal()");

  }

  protected void dispose() {

    System.out.println("Animal dispose");

    t.dispose();

    p.dispose();

    super.dispose();

  }

}

 

class Amphibian extends Animal {

  private Characteristic p =

    new Characteristic("can live in water");

  private Description t =

    new Description("Both water and land");

  Amphibian() {

    System.out.println("Amphibian()");

  }

  protected void dispose() {

    System.out.println("Amphibian dispose");

    t.dispose();

    p.dispose();

    super.dispose();

  }

}

 

public class Frog extends Amphibian {

  private static Test monitor = new Test();

  private Characteristic p = new Characteristic("Croaks");

  private Description t = new Description("Eats Bugs");

  public Frog() {

    System.out.println("Frog()");

  }

  protected void dispose() {

    System.out.println("Frog dispose");

    t.dispose();

    p.dispose();

    super.dispose();

  }

  public static void main(String[] args) {

    Frog frog = new Frog();

    System.out.println("Bye!");

    frog.dispose();

    monitor.expect(new String[] {

      "Creating Characteristic is alive",

      "Creating Description Basic Living Creature",

      "LivingCreature()",

      "Creating Characteristic has heart",

      "Creating Description Animal not Vegetable",

      "Animal()",

      "Creating Characteristic can live in water",

      "Creating Description Both water and land",

      "Amphibian()",

      "Creating Characteristic Croaks",

      "Creating Description Eats Bugs",

      "Frog()",

      "Bye!",

      "Frog dispose",

      "finalizing Description Eats Bugs",

      "finalizing Characteristic Croaks",

      "Amphibian dispose",

      "finalizing Description Both water and land",

      "finalizing Characteristic can live in water",

      "Animal dispose",

      "finalizing Description Animal not Vegetable",

      "finalizing Characteristic has heart",

      "LivingCreature dispose",

      "finalizing Description Basic Living Creature",

      "finalizing Characteristic is alive"

    });

  }

}

Each class in the hierarchy also contains a member objects of types Characteristic and Description, which must also be disposed. The order of disposal should be the reverse of the order of initialization, in case one subobject is dependent on another. For fields, this means the reverse of the order of declaration (since fields are initialized in declaration order). For base classes (following the form that’s used in C++ for destructors), you should perform the derived-class cleanup first, then the base-class cleanup. That’s because the derived-class cleanup could call some methods in the base class that require the base-class components to be alive, so you must not destroy them prematurely. From the output you can see that all parts of the Frog object are disposed in reverse order of creation.

在继承关系中的每个类都包含了CharacteristticDescription类的很多对象,它们同样也需要被释放。对象之间是存在依赖关系的,因此释放的顺序和初始化的顺序是正好相反的,也就是说释放的顺序应该和声明的顺序正好相反,因为数据项是按照声明的办法初始化的。对于基类(它采用了C++的析构函数的形式),你需要现执行派生类的清理工作然后再是基类的清理工作。这是因为派生类的对象可能会调用基类的某些方法,所以需要基类对象必须存在,所以你不能过早的释放掉它们。通过输出的结果你可用年点Frog对象释放的顺序和创建的顺序相反。

From this example, you can see that although you don’t always need to perform cleanup, when you do, the process requires care and awareness.

通过这个例子,虽然你不需要经常的执行清理动作,但是当你需要的时候,这个过程你还是需要知道和小心的。

Behavior of polymorphic methods inside constructors

The hierarchy of constructor calls brings up an interesting dilemma. What happens if you’re inside a constructor and you call a dynamically-bound method of the object being constructed? Inside an ordinary method, you can imagine what will happen: The dynamically-bound call is resolved at run time, because the object cannot know whether it belongs to the class that the method is in or some class derived from it. For consistency, you might think this is what should happen inside constructors.

构造方法的调用顺序引出了一个很有趣的现象。如果你再一个构造方法中调用正在创建的这个对象的一个动态绑定的方法会发生什么呢?如果是在一个普通的方法中,你可以设想应该是这样:动态绑定的调用只有在运行时才决定,因为对象不知道它属于当前所在类的方法还是在这个类的派生类中。按照一致性考虑的话,你可能认为构造方法内也应该是这样执行。

This is not exactly the case. If you call a dynamically-bound method inside a constructor, the overridden definition for that method is used. However, the effect can be rather unexpected and can conceal some difficult-to-find bugs.

这不是真正的情况。如果你在构造方法中调用了一个动态绑定的方法,那么会用覆写后的那个方法。然而,这样的影响超出了我们的预想,并且会隐藏很多难以发现的bugs

Conceptually, the constructor’s job is to bring the object into existence (which is hardly an ordinary feat). Inside any constructor, the entire object might be only partially formed—you can know only that the base-class objects have been initialized, but you cannot know which classes are inherited from you. A dynamically bound method call, however, reaches “outward” into the inheritance hierarchy. It calls a method in a derived class. If you do this inside a constructor, you call a method that might manipulate members that haven’t been initialized yet—a sure recipe for disaster.

概念性的来讲,构造方法只是为了创建对象(这并不是很简单的事情)。在构造方法中,对象很可能只是创建了一部分,你知道的仅仅是基类部分初始化了,但是不知道又继承出了什么。动态绑定方法的调用,然而,这将涉及到继承类图。它调用了它派生类中的方法。如果你在构造方法中这么作的话可能会访问到一个没有创建初始化的成员,  这肯定是会出问题的。

You can see the problem in the following example:

import com.bruceeckel.simpletest.*;

abstract class Glyph {

  abstract void draw();

  Glyph() {

    System.out.println("Glyph() before draw()");

    draw();

    System.out.println("Glyph() after draw()");

  }

}

 

class RoundGlyph extends Glyph {

  private int radius = 1;

  RoundGlyph(int r) {

    radius = r;

    System.out.println(

      "RoundGlyph.RoundGlyph(), radius = " + radius);

  }

  void draw() {

    System.out.println(

      "RoundGlyph.draw(), radius = " + radius);

  }

}

 

public class PolyConstructors {

  private static Test monitor = new Test();

  public static void main(String[] args) {

    new RoundGlyph(5);

    monitor.expect(new String[] {

      "Glyph() before draw()",

      "RoundGlyph.draw(), radius = 0",

      "Glyph() after draw()",

      "RoundGlyph.RoundGlyph(), radius = 5"

    });

  }

}

In Glyph, the draw( ) method is abstract, so it is designed to be overridden. Indeed, you are forced to override it in RoundGlyph. But the Glyph constructor calls this method, and the call ends up in RoundGlyph.draw( ), which would seem to be the intent. But if you look at the output, you can see that when Glyph’s constructor calls draw( ), the value of radius isn’t even the default initial value 1. It’s 0. This would probably result in either a dot or nothing at all being drawn on the screen, and you’d be left staring, trying to figure out why the program won’t work.

Glyph类中,draw()方法是abstract的,所以它设计的目的就是为了被覆写,事实上也确实在RoundGlyph中对该方法尽心了覆写。但是在基类Glyph中对该方法进行了调用,而这个方法最终以调用RoundGlay.draw()方法而结束,这一切看起来是正常的。但是你可以看到当Glyph调用draw()的时候radius的值不是默认的初始值,而是0。这会造成结果在屏幕上可能是个点或者什么都没有,而你只能目不转睛的试图找出为什么程序会这样执行。

The order of initialization described in the earlier section isn’t quite complete, and that’s the key to solving the mystery. The actual process of initialization is:

关于初始化的顺序的描述在以前早些的章节中描述的并不是很完善,而这恰恰是揭开这层神秘面纱的关键。完整的初始化的过程应该是如下这样:

  1. The storage allocated for the object is initialized to binary zero before anything else happens.  
  2. The base-class constructors are called as described previously. At this point, the overridden draw( ) method is called (yes, before the RoundGlyph constructor is called), which discovers a radius value of zero, due to Step 1.  
  3. Member initializers are called in the order of declaration.  
  4. The body of the derived-class constructor is called.

1. 在进行所有的工作之前所有的对象分配的内存会被初始化为二进制的0

2. 在前面的章节提到的会先调用基类的构造方法,在这点上,派生类覆写的draw()方法被调用,而且是在RoundGlyph构造方法被执行之前,受到第一步的影响radius的值是0

3. 成员的初始化和声明的顺序相同;

4. 调用派生类的构造方法;

There’s an upside to this, which is that everything is at least initialized to zero (or whatever zero means for that particular data type) and not just left as garbage. This includes object references that are embedded inside a class via composition, which become null. So if you forget to initialize that reference, you’ll get an exception at run time. Everything else gets zero, which is usually a telltale value when looking at output.

所有的都被初始化为0这是有一定的优势的(无论什么类型都被初始化为0),这样不会留下垃圾。这也包含哪些采用合成技术生成的对象,被初始化为null,所以当你忘记初始化这个对象的时候,在运行时你会得到一个异常信息。其它所有的都被初始化为0,这样在查看输出结果的时候是有据可循的。

On the other hand, you should be pretty horrified at the outcome of this program. You’ve done a perfectly logical thing, and yet the behavior is mysteriously wrong, with no complaints from the compiler. (C++ produces more rational behavior in this situation.) Bugs like this could easily be buried and take a long time to discover.

从另一方面来说,你会因为程序的这个输出结果感到很惊诧。你作了一个完美的逻辑但是这个输出的结果却很奇怪,编译器也没有提示任何信息(C++在这样的情况下会产生更理性的行为输出),错误很容易的就发生了但是可能需要你化肥很长的时间去解决。

As a result, a good guideline for constructors is, “Do as little as possible to set the object into a good state, and if you can possibly avoid it, don’t call any methods.” The only safe methods to call inside a constructor are those that are final in the base class. (This also applies to private methods, which are automatically final.) These cannot be overridden and thus cannot produce this kind of surprise.

总结的来说构造方法应该完成的是“作尽可能少的事情把对象创建好,尽可能的去避免调用任何方法”,在构造方法中调用那些标识为final的安全的方法,当然也包含private的方法因为默认为final了,这些方法不能被覆写所以也就不会产生这么奇怪的问题了。

Designing with inheritance

Once you learn about polymorphism, it can seem that everything ought to be inherited, because polymorphism is such a clever tool. This can burden your designs; in fact, if you choose inheritance first when you’re using an existing class to make a new class, things can become needlessly complicated.

一旦你学习了多态,看起来就像所有的都应该采用继承技术,因为多态是如此智能的工具。这样会加大你设计的负担;事实上,当你遇到使用一个存在的类来创建一个新类的时候你首先选择的是继承,事情往往会变得很复杂。

A better approach is to choose composition first, especially when it’s not obvious which one you should use. Composition does not force a design into an inheritance hierarchy. But composition is also more flexible since it’s possible to dynamically choose a type (and thus behavior) when using composition, whereas inheritance requires an exact type to be known at compile time. The following example illustrates this:

最好的办法是首先采用合成技术,尤其是你不知道继承哪个类的时候。合成不会让你的设计陷入一个继承关系图中。但是合成还是很灵活的,因为它是动态的选择类型和行为的,而继承则必须在编译期间就需要明确的类型。下面例子论证了这点:

import com.bruceeckel.simpletest.*;

 

abstract class Actor {

  public abstract void act();

}

 

class HappyActor extends Actor {

  public void act() {

    System.out.println("HappyActor");

  }

}

 

class SadActor extends Actor {

  public void act() {

    System.out.println("SadActor");

  }

}

 

class Stage {

  private Actor actor = new HappyActor();

  public void change() { actor = new SadActor(); }

  public void performPlay() { actor.act(); }

}

 

public class Transmogrify {

  private static Test monitor = new Test();

  public static void main(String[] args) {

    Stage stage = new Stage();

    stage.performPlay();

    stage.change();

    stage.performPlay();

    monitor.expect(new String[] {

      "HappyActor",

      "SadActor"

    });

  }

}

A Stage object contains a reference to an Actor, which is initialized to a HappyActor object. This means performPlay( ) produces a particular behavior. But since a reference can be rebound to a different object at run time, a reference for a SadActor object can be substituted in actor, and then the behavior produced by performPlay( ) changes. Thus you gain dynamic flexibility at run time. (This is also called the State Pattern. See Thinking in Patterns (with Java) at www.BruceEckel.com.) In contrast, you can’t decide to inherit differently at run time; that must be completely determined at compile time.

Stage对象中包含了一个Actor对象,这个对象被初始化为了一个HappyActor的对象。这意味着performPlay()会产生特定的行为。但是在运行期间它可以被赋值为其它的对象,它可以被SadActor对象替换,因为它们都是Actor的对象,这时performPlay()产生的行为就发生了变化。因此你尝到了多态在运行时绑定的甜头。反过来看,在运行时你不能再去继承不同的类,这些必须在编译的时候就确定下来。

A general guideline is “Use inheritance to express differences in behavior, and fields to express variations in state.” In the preceding example, both are used; two different classes are inherited to express the difference in the act( ) method, and Stage uses composition to allow its state to be changed. In this case, that change in state happens to produce a change in behavior.

一个一般的准则是“使用继承来表示不同的行为,而数据成员则来表示不同的状态”,在上面的章节中我们都用到了。两个不同的继承下来的类在act()方法中行为确实不同,而Stage类则采用合成技术允许它们互换,而状态上的变换则带来了行为上的变化。

Pure inheritance vs. extension

When studying inheritance, it would seem that the cleanest way to create an inheritance hierarchy is to take the “pure” approach. That is, only methods that have been established in the base class or interface are to be overridden in the derived class, as seen in this diagram:

This can be called a pure “is-a” relationship because the interface of a class establishes what it is. Inheritance guarantees that any derived class will have the interface of the base class and nothing less. If you follow this diagram, derived classes will also have no more than the base-class interface.

当你学习继承的时候,研究继承的最好的办法就是创建一个纯继承关系图。在基类或者接口中只是声明这些不允许被覆写的方法,就像下面这张图:

This can be thought of as pure substitution, because derived class objects can be perfectly substituted for the base class, and you never need to know any extra information about the subclasses when you’re using them:

这可以被看作是纯的替换,因为派生类的对象可以完美的被替换为基类的对象,当你使用的时候你不需要知道派生类的额外的一些信息。

That is, the base class can receive any message you can send to the derived class because the two have exactly the same interface. All you need to do is upcast from the derived class and never look back to see what exact type of object you’re dealing with. Everything is handled through polymorphism.

基类对象可用接受到所有的你发送给派生类对象的指示,因为两个对象拥有完全相同的接口。你只需要将对象上传转化为基类对象不需要知道额外的信息,而这些都是通过多态实现的。

When you see it this way, it seems like a pure is-a relationship is the only sensible way to do things, and any other design indicates muddled thinking and is by definition broken. This too is a trap. As soon as you start thinking this way, you’ll turn around and discover that extending the interface (which, unfortunately, the keyword extends seems to encourage) is the perfect solution to a particular problem. This could be termed an “is-like-a” relationship, because the derived class is like the base class—it has the same fundamental interface—but it has other features that require additional methods to implement:

这样看来的话,“is-a”的关系是一种很明智的思路来作事情,而其它的设计则是混乱无章的。这是一种误解,当你开始这样思考问题,你会回过头来发现扩展接口对很多问题来讲也是很好的解决方案。这可以被看作是“is like a”的关系,因为派生类的对象像基类的对象,它拥有同样的接口,而派生类对象又拥有一些特征的方法来实现:

While this is also a useful and sensible approach (depending on the situation), it has a drawback. The extended part of the interface in the derived class is not available from the base class, so once you upcast, you can’t call the new methods:

当这称为一个很有用而且可以来解决某些问题的明智的手段的时候,它也有一些弊端。这些在派生类中扩展出来的部分在基类是无效的,所以你一旦上传转化你则不能再调用这些方法。

If you’re not upcasting in this case, it won’t bother you, but often you’ll get into a situation in which you need to rediscover the exact type of the object so you can access the extended methods of that type. The following section shows how this is done.

如果你不需要上传转化的话,这个问题不会缠绕你,但是大部分时候你会遇到你需要得到对象的确切类型信息依赖访问派生类中扩展的方法。下面的章节中会展现:

Downcasting and run-time type identification

Since you lose the specific type information via an upcast (moving up the inheritance hierarchy), it makes sense that to retrieve the type information—that is, to move back down the inheritance hierarchy—you use a downcast. However, you know an upcast is always safe; the base class cannot have a bigger interface than the derived class. Therefore, every message you send through the base class interface is guaranteed to be accepted. But with a downcast, you don’t really know that a shape (for example) is actually a circle. It could instead be a triangle or square or some other type.

通过采用上传转化的技术你丢失了对象本来所属的类型信息,那就意味着如果你需要重新得到对象的类型的话就的使用downcast顺着继承的关系下移。然而你是知道的上传转化是安全的,因为基类不可能拥有比派生类更宽泛的接口,所以发送给基类接口的信息都会被正确的传达。但是下传转化你则不清楚shape是否是一个circle。它可能是一个triangle或者suqare再或者其它的形状。

To solve this problem, there must be some way to guarantee that a downcast is correct, so that you won’t accidentally cast to the wrong type and then send a message that the object can’t accept. This would be quite unsafe.

要解决这个问题,你必须有一些方式能够保证下传转化是正确的,这样就不会发生转化为错误的类型,并且发送这个对象无法接受的信息,这将很不安全。

In some languages (like C++) you must perform a special operation in order to get a type-safe downcast, but in Java, every cast is checked! So even though it looks like you’re just performing an ordinary parenthesized cast, at run time this cast is checked to ensure that it is in fact the type you think it is. If it isn’t, you get a ClassCastException. This act of checking types at run time is called run-time type identification (RTTI). The following example demonstrates the behavior of RTTI:

在诸如C++的语言中,你必须使用一个特殊的操作符来获得一个安全的下传转化,但是在Java中所有的转换都是要校验的,所以及时看起来仅仅是加上一个括号操作符的转化,在运行期间也是要校验的来确保这是你所期望的类。如果不是的话你将会得到一个ClassCastException错误。在运行时校验类型的方法叫做run-time type identification (RTTI)。下面的例子就是来论证这个观点的:

class Useful {

  public void f() {}

  public void g() {}

}

 

class MoreUseful extends Useful {

  public void f() {}

  public void g() {}

  public void u() {}

  public void v() {}

  public void w() {}

}

 

public class RTTI {

  public static void main(String[] args) {

    Useful[] x = {

      new Useful(),

      new MoreUseful()

    };

    x[0].f();

    x[1].g();

    ((MoreUseful)x[1]).u();

    ((MoreUseful)x[0]).u();

  }

}

As in the diagram, MoreUseful extends the interface of Useful. But since it’s inherited, it can also be upcast to a Useful. You can see this happening in the initialization of the array x in main( ). Since both objects in the array are of class Useful, you can send the f( ) and g( ) methods to both, and if you try to call u( ) (which exists only in MoreUseful), you’ll get a compile-time error message.

正如

正如图中所表示的MoreUseful扩展了Useful的接口。因为采用了继承所以你可以将他的对象上传转化为Useful的对象。你可以在初始化x数组的时候看到。因为数组中的两个对象都是Useful的,所以你可以给两个对象发送f()g()的方法,但是如果你想调用u()(只是在派生类MoreUseful中存在),你将会得到一个编译时错误。

If you want to access the extended interface of a MoreUseful object, you can try to downcast. If it’s the correct type, it will be successful. Otherwise, you’ll get a ClassCastException. You don’t need to write any special code for this exception, since it indicates a programmer error that could happen anywhere in a program.

如果你希望访问扩展接口后的MoreUseful的对象,你可以采用下传转化。如果是正确的类型,这将会成功。否则你将会得到一个ClassCastException异常信息。你不需要为该异常信息写特殊的代码,因为它提示程序错误,它会发生在程序的任何一个地方。

There’s more to RTTI than a simple cast. For example, there’s a way to see what type you’re dealing with before you try to downcast it. All of Chapter 10 is devoted to the study of different aspects of Java run-time type identification.

RTTI比这种简单的转化更加复杂。举例来说,有一个办法,可以让你获得在进行下船转化之前你操作的对象的类型,将在本书的第十章详细的介绍运行时类型鉴别。

Summary

Polymorphism means “different forms.” In object-oriented programming, you have the same face (the common interface in the base class) and different forms using that face: the different versions of the dynamically bound methods.

多态意味着不同的形式,在面向对象编程中,拥有同样的名称和不同的形式:各个版本的动态绑定方法。

You’ve seen in this chapter that it’s impossible to understand, or even create, an example of polymorphism without using data abstraction and inheritance. Polymorphism is a feature that cannot be viewed in isolation (like a switch statement can, for example), but instead works only in concert, as part of a “big picture” of class relationships. People are often confused by other, non-object-oriented features of Java, like method overloading, which are sometimes presented as object-oriented. Don’t be fooled: If it isn’t late binding, it isn’t polymorphism.

在本章中如果你不具有抽象和继承的知识的话那么理解多态,甚至创建多态的例子几乎是不可能的。多态的特性是不能孤立存在的(例如switch语法),多态只有在类关系的大背景下才能发挥出威力。人们经常把它同那些非面向对象的特征混淆,例如重载,经常被作为面向对象的特征。不要上当:如果不是事后绑定的话就不是多态。

To use polymorphism—and thus object-oriented techniques—effectively in your programs, you must expand your view of programming to include not just members and messages of an individual class, but also the commonality among classes and their relationships with each other. Although this requires significant effort, it’s a worthy struggle, because the results are faster program development, better code organization, extensible programs, and easier code maintenance.

如果希望在程序中有效的使用了面向对象的多态机制,你必须扩展自己的视野,不仅仅局限在类成员和消息上,要去理解类之间的共性以及彼此之间的关系。虽然这个要求很高,但是还是值得的,因为它加快了开发进度,优化了代码结构,增强了扩展性,使得维护更简单。

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值