Polymorphism【1】

Polymorphism is the third essential feature of an object-oriented programming language, after data abstraction and inheritance.

It provides another dimension of separation of interface from implementation, to decouple what from how. Polymorphism allows improved code organization and readability as well as the creation of extensible programs that can be “grown” not only during the original creation of the project, but also when new features are desired.

多態性是面嚮對象語言中繼封裝,繼承之後第三個基本特徵。它在另一個層面實現了接口和實現的分離,也就是說分開了做什麼和怎麼做,多態性不但可以提高代碼的組織結構和易讀性,這樣你就可以創建出可擴展的程序,不僅僅是在創建程序的時候可以擴展,而且還可以在增加新的特性的時候擴展。

Encapsulation creates new data types by combining characteristics and behaviors. Implementation hiding separates the interface from the implementation by making the details private. This sort of mechanical organization makes ready sense to someone with a procedural programming background. But polymorphism deals with decoupling in terms of types. In the last chapter, you saw how inheritance allows the treatment of an object as its own type or its base type. This ability is critical because it allows many types (derived from the same base type) to be treated as if they were one type, and a single piece of code to work on all those different types equally. The polymorphic method call allows one type to express its distinction from another, similar type, as long as they’re both derived from the same base type. This distinction is expressed through differences in behavior of the methods that you can call through the base class.

封裝是通過綁定有特徵的數據和行爲,創建一個新的數據類型。隱藏實現通過設置明細爲private將實現和接口部分分離開來,這種機械呆板的講解方式主要是爲了那些習慣於過程式開發的程序員。但是多態是站在類的角度來處理這種邏輯上的分離的,在上一章節中,我們知道了如何讓一個對象作爲自己或者以自己的基類的方式來處理。這種能力是很關鍵的,它可以讓許多類(繼承自同一個類)來作爲一個類來處理。這樣一段代碼就可以作用於不同的類了,多態性的方法的調用能看出兩個相似的類之間的差別,甚至於兩個類繼承自同一個基類。這種區別你通過調用基類的一個方法得到不同的行爲而證實。

In this chapter, you’ll learn about polymorphism (also called dynamic binding or late binding or run-time binding) starting from the basics, with simple examples that strip away everything but the polymorphic behavior of the program.

在本章,你將通過一些只有多態方法的小例子學習到多態性(動態綁定,後綁定或者運行時綁定)。

Upcasting revisited

In Chapter 6 you saw how an object can be used as its own type or as an object of its base type. Taking an object reference and treating it as a reference to its base type is called upcasting because of the way inheritance trees are drawn with the base class at the top.

You also saw a problem arise, which is embodied in the following example about musical instruments. Since several examples play Notes, we should create the Note class separately, in a package:

在第六章你知道了如何將一個對象轉化爲自己的類型或者它的基類的對象。將一個對象的reference轉化爲它的基類的reference來處理,叫做“上傳”。因爲在繼承關係圖中基類總是在最頂部。你會看到問題也出來了,已經在下面關於樂器的例子中體現出來了,因爲幾個例子都要使用音符Note,所以我們建立了一個Note類。

package c07.music;

import com.bruceeckel.simpletest.*;

 

public class Note {

  private String noteName;

  private Note(String noteName) {

    this.noteName = noteName;

  }

  public String toString() { return noteName; }

  public static final Note

    MIDDLE_C = new Note("Middle C"),

    C_SHARP  = new Note("C Sharp"),

    B_FLAT   = new Note("B Flat");

}

This is an “enumeration” class, which has a fixed number of constant objects to choose from. You can’t make additional objects because the constructor is private.

In the following example, Wind is a type of Instrument, therefore Wind is inherited from Instrument:

這是一個枚舉類,它創建了幾個固定的對象顧你選擇,你不能創建其它的對象,因爲這個類的構造方法是私有的。下面的例子中,你可以看到Wind是一種樂器,因爲它繼承了Instrument類。

package c07.music;

import com.bruceeckel.simpletest.*;

 

public class Music {

  private static Test monitor = new Test();

  public static void tune(Instrument i) {

    i.play(Note.MIDDLE_C);

  }

  public static void main(String[] args) {

    Wind flute = new Wind();

tune(flute);   

monitor.expect(new String[] {

      "Wind.play() Middle C"

    });

  }

}

 

package c07.music;

 

public class Wind extends Instrument {

  public void play(Note n) {

    System.out.println("Wind.play() " + n);

  }

}

 

The method Music.tune( ) accepts an Instrument reference, but also anything derived from Instrument. In main( ), you can see this happening as a Wind reference is passed to tune( ), with no cast necessary. This is acceptable—the interface in Instrument must exist in Wind, because Wind is inherited from Instrument. Upcasting from Wind to Instrument may “narrow” that interface, but it cannot make it anything less than the full interface to Instrument.

這個 Music.tune( )只接受Instrument類的對象,但是也可以接受任何Instrument的派生類的對象。在主方法中,Wind對象在沒有進行上傳轉化的情況下也調用了tune()方法。這是可行的,因爲Wind繼承了Instrument類並且實現了它的接口,將Wind上傳轉化爲Instrument將接口變窄了,但是再窄也不比它的基類Instrument少。

Forgetting the object type

Music.java might seem strange to you. Why should anyone intentionally forget the type of an object? This is what happens when you upcast, and it seems like it could be much more straightforward if tune( ) simply takes a Wind reference as its argument. This brings up an essential point: If you did that, you’d need to write a new tune( ) for every type of Instrument in your system. Suppose we follow this reasoning and add Stringed and Brass instruments:

 Music.java這個寫法對你來說可能是陌生的,爲什麼需要每個對象忽略自己的類別呢,上面的例子就是再做這個事情,但是看起來讓tune()方法接受Wind的對象好像更簡單一些,這就帶來了一個很本質的觀點:如果那樣的話你就需要再爲每個類寫一個新的方法。假設順着這個思路我們再增加一個Stringed類和Brsaa類呢?

package c07.music;

import com.bruceeckel.simpletest.*;

 

class Stringed extends Instrument {

  public void play(Note n) {

    System.out.println("Stringed.play() " + n);

  }

}

 

class Brass extends Instrument {

  public void play(Note n) {

    System.out.println("Brass.play() " + n);

  }

}

 

public class Music2 {

  private static Test monitor = new Test();

  public static void tune(Wind i) {

    i.play(Note.MIDDLE_C);

  }

  public static void tune(Stringed i) {

    i.play(Note.MIDDLE_C);

  }

  public static void tune(Brass i) {

    i.play(Note.MIDDLE_C);

  }

  public static void main(String[] args) {

    Wind flute = new Wind();

    Stringed violin = new Stringed();

    Brass frenchHorn = new Brass();

    tune(flute); // No upcasting

    tune(violin);

    tune(frenchHorn);

    monitor.expect(new String[] {

      "Wind.play() Middle C",

      "Stringed.play() Middle C",

      "Brass.play() Middle C"

    });

  }

}

This works, but there’s a major drawback: you must write type-specific methods for each new Instrument class you add. This means more programming in the first place, but it also means that if you want to add a new method like tune( ) or a new type of Instrument, you’ve got a lot of work to do. Add the fact that the compiler won’t give you any error messages if you forget to overload one of your methods and the whole process of working with types becomes unmanageable.

這樣可以實現,但是有很多的弊端:你必須爲你增加的每個樂器增加該類別特有的方法。這樣首先會有很多的代碼,也同樣意味着你如果增加一個tune()方法或者增加Instrument的派生類的時候你就有許多的代碼要去做,如果你忘記了重載某個方法的話編譯器是不會報錯的,但是這個程序的整體處理流程可能就亂套了。

Wouldn’t it be much nicer if you could just write a single method that takes the base class as its argument, and not any of the specific derived classes? That is, wouldn’t it be nice if you could forget that there are derived classes, and write your code to talk only to the base class?

That’s exactly what polymorphism allows you to do. However, most programmers who come from a procedural programming background have a bit of trouble with the way polymorphism works.

如果你只在基類當中寫一個以基類作爲參數的方法不是更好嗎?並不是爲每一個派生類都寫一個方法,如果你忽略所有派生類的類型並且僅僅對基類做處理不是更好嗎?這就讓我們很明白多態允許我們作些什麼了,但是大部分以過程式開發的程序員們會對使用多態有些牴觸。

The twist

The difficulty with Music.java can be seen by running the program. The output is Wind.play( ). This is clearly the desired output, but it doesn’t seem to make sense that it would work that way. Look at the tune( ) method:

當運行程序的時候Music.java還是比較讓人費解,輸出的結果是Wind.play().這是我們所期望的,但是程序看起來並不是那樣執行的,讓我們來看tune()這個方法:

  public static void tune(Instrument i) {

    i.play(Note.MIDDLE_C);

  }

 

It receives an Instrument reference. So how can the compiler possibly know that this Instrument reference points to a Wind in this case and not a Brass or Stringed? The compiler can’t. To get a deeper understanding of the issue, it’s helpful to examine the subject of binding.

 這個方法接受一個Instrument對象。但是編譯器如何知道的調用這個方法的是Wind對象而不是Brass或者Stringed呢?編譯器不知道,如果想深入的理解這個問題,學習綁定這項知識很有幫助。

Method-call binding

Connecting a method call to a method body is called binding. When binding is performed before the program is run (by the compiler and linker, if there is one), it’s called early binding. You might not have heard the term before because it has never been an option with procedural languages. C compilers have only one kind of method call, and that’s early binding.

The confusing part of the preceding program revolves around early binding, because the compiler cannot know the correct method to call when it has only an Instrument reference.

將一個方法的調用連接到一個方法本身這叫做“綁定”。如果綁定是在程序運行的時候(編譯或者Linker的時候),叫做“事前綁定”。你可能沒有聽說過這個說法,因爲在過程式語言中這不是一個選項。C編譯器也只有一種綁定方式就是事前綁定。在上面的問題中就是因爲事前綁定,因爲當一個Instrument的對象調用的時候,編譯器也不知道什麼類型的對象在調用。

The solution is called late binding, which means that the binding occurs at run time, based on the type of object. Late binding is also called dynamic binding or run-time binding. When a language implements late binding, there must be some mechanism to determine the type of the object at run time and to call the appropriate method. That is, the compiler still doesn’t know the object type, but the method-call mechanism finds out and calls the correct method body. The late-binding mechanism varies from language to language, but you can imagine that some sort of type information must be installed in the objects.

還有一種叫做事後綁定,即程序在運行的時候根據對象的類型才綁定,事後綁定又叫做動態綁定或者運行時綁定,如果一個語言的實現部分是後綁定,那麼那一定有某些機制能夠在程序運行的時候判斷對象的類別,然後取調用相關的方法。後綁定機制在不同的語言之間也不同,但是我們可以想想得出對象中一定存着某些所屬類型的信息。

All method binding in Java uses late binding unless the method is static or final (private methods are implicitly final). This means that ordinarily you don’t need to make any decisions about whether late binding will occur—it happens automatically.

Java中所有的綁定都是屬於事後綁定,除非是static或者finalprivate暗含了private)。所以一般來講你沒有必要進行特別的說明,程序默認就是事後綁定。

Why would you declare a method final? As noted in the last chapter, it prevents anyone from overriding that method. Perhaps more important, it effectively “turns off” dynamic binding, or rather it tells the compiler that dynamic binding isn’t necessary. This allows the compiler to generate slightly more efficient code for final method calls. However, in most cases it won’t make any overall performance difference in your program, so it’s best to only use final as a design decision, and not as an attempt to improve performance.

那爲什麼要聲明一個final的方法呢?我們已經在上一章中進行了介紹,它禁止別人覆寫那個方法,可能更重要的是它有效的關閉了動態綁定,或者它告訴了編譯器,這裏不需要動態綁定,這樣編譯器就能爲final的方法的調用產生更爲高效的代碼,然後在大多情況下,它不會對你的程序性能有多大的提高,所以final最好還是作爲一種設計概念,不要對它提高性能寄予希望。

Producing the right behavior

Once you know that all method binding in Java happens polymorphically via late binding, you can write your code to talk to the base class and know that all the derived-class cases will work correctly using the same code. Or to put it another way, you “send a message to an object and let the object figure out the right thing to do.”

The classic example in OOP is the “shape” example. This is commonly used because it is easy to visualize, but unfortunately it can confuse novice programmers into thinking that OOP is just for graphics programming, which is of course not the case.

一旦你知道了在Java中的綁定都是事後綁定,你就可以放心的以基類對象作爲參數寫代碼,並且這樣派生類的對象也可以使用同樣的代碼被處理的正確,或者說,你給對象發送一個消息,讓它自己去做正確的事情。在學習面向對象的時候“Shape”是一個經典的例子,這個經常的被使用是因爲它比較形象,但是不幸的是這個例子很容易搞暈新手,他們誤以爲面向對象只是爲了形狀編程,當然,不僅僅是這樣的。

The shape example has a base class called Shape and various derived types: Circle, Square, Triangle, etc. The reason the example works so well is that it’s easy to say “a circle is a type of shape” and be understood. The inheritance diagram shows the relationships:

形狀的這個例子有一個Shape的基類,並且許多的繼承類,CircleSquareTriangle等等。這個例子可以使用的很好是因爲很容易的就可以理解“圓是一種形狀。下面的繼承圖很好的展現了這種關係:

The upcast could occur in a statement as simple as:

Shape s = new Circle();

Here, a Circle object is created, and the resulting reference is immediately assigned to a Shape, which would seem to be an error (assigning one type to another); and yet it’s fine because a Circle is a Shape by inheritance. So the compiler agrees with the statement and doesn’t issue an error message.

Suppose you call one of the base-class methods (that have been overridden in the derived classes):

在這裏,創建了一個Circle的對象,但是很快被轉化成了並賦值給了Shape的對象,這樣看起來好像是一個錯誤;但是在這裏是允許的,因爲Circle是繼承自Shape。所以編譯器允許這種語法並且不會提示錯誤信心。假設你調用一個基類的方法(這個方法在派生類中已經被覆寫了)

s.draw();

Again, you might expect that Shape’s draw( ) is called because this is, after all, a Shape reference—so how could the compiler know to do anything else? And yet the proper Circle.draw( ) is called because of late binding (polymorphism).

這次,你可能以爲調用的是Shapedraw()方法,因爲它就是一個Shape的對象,但是編譯器如何確定調用哪個呢?實際上因爲動態綁定的機制調用的是Circledraw()方法。

The following example puts it a slightly different way:

import com.bruceeckel.simpletest.*;

import java.util.*;

 

class Shape {

  void draw() {}

  void erase() {}

}

 

class Circle extends Shape {

  void draw() {

    System.out.println("Circle.draw()");

  }

  void erase() {

    System.out.println("Circle.erase()");

  }

}

 

class Square extends Shape {

  void draw() {

    System.out.println("Square.draw()");

  }

  void erase() {

    System.out.println("Square.erase()");

  }

}

 

class Triangle extends Shape {

  void draw() {

    System.out.println("Triangle.draw()");

  }

  void erase() {

    System.out.println("Triangle.erase()");

  }

}

 

class RandomShapeGenerator {

  private Random rand = new Random();

  public Shape next() {

    switch(rand.nextInt(3)) {

      default:

      case 0: return new Circle();

      case 1: return new Square();

      case 2: return new Triangle();

    }

  }

}

 

public class Shapes {

  private static Test monitor = new Test();

  private static RandomShapeGenerator gen =

    new RandomShapeGenerator();

  public static void main(String[] args) {

    Shape[] s = new Shape[9];

    for(int i = 0; i < s.length; i++)

      s[i] = gen.next();

    for(int i = 0; i < s.length; i++)

      s[i].draw();

    monitor.expect(new Object[] {

      new TestExpression("%% (Circle|Square|Triangle)"

        + "//.draw//(//)", s.length)

    });

  }

}

 

The base class Shape establishes the common interface to anything inherited from Shape—that is, all shapes can be drawn and erased. The derived classes override these definitions to provide unique behavior for each specific type of shape.

RandomShapeGenerator is a kind of “factory” that produces a reference to a randomly-selected Shape object each time you call its next( ) method. Note that the upcasting happens in the return statements, each of which takes a reference to a Circle, Square, or Triangle and sends it out of next( ) as the return type, Shape. So whenever you call next( ), you never get a chance to see what specific type it is, since you always get back a plain Shape reference.

基類“Shape”爲每個繼承“Shape”類的對象提供了通用的接口,所有的“Shape”的對象以及子類的對象都可以調用drawerase方法,派生類中覆寫了所有提供的接口來爲每個派生類的對象提供唯一的方法。RandomShapeGenerator當你調用next()方法的時候來產生隨即的Shape的對象,所有派生類的對象都被進行了上傳轉換動作,你永遠也不可能看到這些對象的實際類型,因爲他們執行後就被強轉爲了基類的對象。

main( ) contains an array of Shape references filled through calls to RandomShapeGenerator.next( ). At this point you know you have Shapes, but you don’t know anything more specific than that (and neither does the compiler). However, when you step through this array and call draw( ) for each one, the correct type-specific behavior magically occurs, as you can see from the output when you run the program.

Main()通過調用RandomShapeGenerator.next( )方法獲得多了很多的Shape對象,在這時候你只是知道自己獲得了Shape對象,但是其它的信息一點也不知道,包括編譯器。但是當你分別使用這些Shape的對象調用draw()方法的時候,運行程序的時候你會看到程序調用正確的行爲。

The point of choosing the shapes randomly is to drive home the understanding that the compiler can have no special knowledge that allows it to make the correct calls at compile time. All the calls to draw( ) must be made through dynamic binding.

之所以要選擇隨即的形狀就是要展示在編譯的時候編譯器也不知道它應該調用哪個方法是正確的,所有關於draw()方法的調用都是動態綁定的。

Extensibility

Now let’s return to the musical instrument example. Because of polymorphism, you can add as many new types as you want to the system without changing the tune( ) method. In a well-designed OOP program, most or all of your methods will follow the model of tune( ) and communicate only with the base-class interface. Such a program is extensible because you can add new functionality by inheriting new data types from the common base class. The methods that manipulate the base-class interface will not need to be changed at all to accommodate the new classes.

現在讓我們重新回到樂器的那個例子,因爲多態,你可以在不修改tune()方法的前提下隨意的增加新的類型,在一個良好的面向對象設計中,大部分的方法應該和例子中的tune()方法一樣,通訊的話只需要調用基類的接口就可以,這樣的程序就是一個可擴展的,因爲他你可以將新類型裏面的繼承下來的方法進行更全面的擴展。這樣那些與基類打交道的對象不需要做任何修改就可以與新繼承下來的對象打交道。

Consider what happens if you take the instrument example and add more methods in the base class and a number of new classes. Here’s the diagram:

可以設想如果繼續那個instrument的例子,你可用爲基類增加很多的派生類和方法,那麼會發生什麼,下面就是這個關係圖:

All these new classes work correctly with the old, unchanged tune( ) method. Even if tune( ) is in a separate file and new methods are added to the interface of Instrument, tune( ) will still work correctly, even without recompiling it. Here is the implementation of the diagram:

在不修改tune()的方法的情況下,這些新增的類可以很好的運轉,即使將tune()方法保存到一個獨立的文件中,給Instrument類增加一些新的接口,tune()仍然可以正常的工作,甚至不需要編譯,下面就是對類關係圖的一個實現。

package c07.music3;

import com.bruceeckel.simpletest.*;

import c07.music.Note;

 

class Instrument {

  void play(Note n) {

    System.out.println("Instrument.play() " + n);

  }

  String what() { return "Instrument"; }

  void adjust() {}

}

 

class Wind extends Instrument {

  void play(Note n) {

    System.out.println("Wind.play() " + n);

  }

  String what() { return "Wind"; }

  void adjust() {}

}

 

class Percussion extends Instrument {

  void play(Note n) {

    System.out.println("Percussion.play() " + n);

  }

  String what() { return "Percussion"; }

  void adjust() {}

}

 

class Stringed extends Instrument {

  void play(Note n) {

    System.out.println("Stringed.play() " + n);

  }

  String what() { return "Stringed"; }

  void adjust() {}

}

 

class Brass extends Wind {

  void play(Note n) {

    System.out.println("Brass.play() " + n);

  }

  void adjust() {

    System.out.println("Brass.adjust()");

  }

}

 

class Woodwind extends Wind {

  void play(Note n) {

    System.out.println("Woodwind.play() " + n);

  }

  String what() { return "Woodwind"; }

}

 

public class Music3 {

  private static Test monitor = new Test();

  public static void tune(Instrument i) {

    i.play(Note.MIDDLE_C);

  }

  public static void tuneAll(Instrument[] e) {

    for(int i = 0; i < e.length; i++)

      tune(e[i]);

  }

  public static void main(String[] args) {

    Instrument[] orchestra = {

      new Wind(),

      new Percussion(),

      new Stringed(),

      new Brass(),

      new Woodwind()

    };

    tuneAll(orchestra);

    monitor.expect(new String[] {

      "Wind.play() Middle C",

      "Percussion.play() Middle C",

      "Stringed.play() Middle C",

      "Brass.play() Middle C",

      "Woodwind.play() Middle C"

    });

  }

}

The new methods are what( ), which returns a String reference with a description of the class, and adjust( ), which provides some way to adjust each instrument.

In main( ), when you place something inside the orchestra array, you automatically upcast to Instrument.

What()新方法可以返回一個對象的類型的描述,而adjust()方法則提供了不同的方式去調整沒見Instrument。在主方法中,當你在orchestra這個容器中放入對象的時候,會自動的將這些對象上傳轉化爲Instrument類的對象。

You can see that the tune( ) method is blissfully ignorant of all the code changes that have happened around it, and yet it works correctly. This is exactly what polymorphism is supposed to provide. Changes in your code don’t cause damage to parts of the program that should not be affected. Put another way, polymorphism is an important technique for the programmer to “separate the things that change from the things that stay the same.”

你可以看到tune()方法對周圍代碼的改動不需要任何的調整,它仍然可以正常的運轉。這恰恰就是多態機制所做到的,當你修改代碼的時候不會對這部分代碼造成影響。或者換一種說話,多態是一種將變化的和不變的東西分割開來的一種很重要的技術。

Pitfall: “overriding” private methods

Here’s something you might innocently try to do:

import com.bruceeckel.simpletest.*;

 

public class PrivateOverride {

  private static Test monitor = new Test();

  private void f() {

    System.out.println("private f()");

  }

  public static void main(String[] args) {

    PrivateOverride po = new Derived();

    po.f();

    monitor.expect(new String[] {

      "private f()"

    });

  }

}

 

class Derived extends PrivateOverride {

  public void f() {

    System.out.println("public f()");

  }

}

 

You might reasonably expect the output to be “public f( )”, but a private method is automatically final, and is also hidden from the derived class. So Derived’s f( ) in this case is a brand new method; it’s not even overloaded, since the base-class version of f( ) isn’t visible in Derived.

你可能很期待的等待輸出的結果是“public f()”,但是private方法即爲final,當然在派生類看來是隱藏的。所以在派生類裏面這種情況會被視爲一個新的方法,因爲基類的private方法在派生類是看不到的。

The result of this is that only non-private methods may be overridden, but you should watch out for the appearance of overriding private methods, which generates no compiler warnings, but doesn’t do what you might expect. To be clear, you should use a different name from a private base-class method in your derived class.

這樣的結果說明了只有非private方法纔可以被覆寫,但是你需要留意那些看上去很像覆寫private的方法,這些不會產生編譯錯誤,但是不會執行你所希望的,爲了代碼更加清晰,在派生類當中你應該命名一個和基類不同的方法名稱。

Abstract classes and methods

In all the instrument examples, the methods in the base class Instrument were always “dummy” methods. If these methods are ever called, you’ve done something wrong. That’s because the intent of Instrument is to create a common interface for all the classes derived from it.

在所有關於Instrument的例子中,Instrument類中的方法都不是真正的方法,如果這些方法被調用的話將會出現錯誤信息。因爲Instrument類的目的只是爲了所有的派生類建立一個公共的接口。

The only reason to establish this common interface is so it can be expressed differently for each different subtype. It establishes a basic form, so you can say what’s in common with all the derived classes. Another way of saying this is to call Instrument an abstract base class (or simply an abstract class). You create an abstract class when you want to manipulate a set of classes through this common interface. All derived-class methods that match the signature of the base-class declaration will be called using the dynamic binding mechanism. (However, as seen in the last section, if the method’s name is the same as the base class but the arguments are different, you’ve got overloading, which probably isn’t what you want.)

創建公共接口的唯一目的是因爲接口是因爲同樣的接口可以爲不同的派生類對象提供不同的行爲。它只是建立一個基本的框架,你可以說這是所有的派生類都有的,換一種說法你可以叫Instrument爲抽象的基類或者簡化爲抽象類。你創建了一個公共接口,你可以通過這個接口操作很多這個接口的派生類。在派生類中所有與基類的參數聲明相同的方法在調用的時候都會啓用動態綁定機制。(然而你會在最後一章中看到,如果方法的名字和基類相同,但是參數不同的話這被稱之爲重載,這可能不是你所期望的)

If you have an abstract class like Instrument, objects of that class almost always have no meaning. That is, Instrument is meant to express only the interface, and not a particular implementation, so creating an Instrument object makes no sense, and you’ll probably want to prevent the user from doing it. This can be accomplished by making all the methods in Instrument print error messages, but that delays the information until run time and requires reliable exhaustive testing on the user’s part. It’s better to catch problems at compile time.

如果你有一個像Instrument的類,這種類的對象是沒有任何意義的,Instrument僅僅是爲了表示一個接口,並沒有詳細的實現,所以創建Instrument的對象沒有任何意義,你可能像禁止用戶這麼做,你可以通過在方法中打印錯誤信息來完成,但是這樣比較晚了,因爲這樣需要程序運行的時候進行非常詳盡的測試才行。而最好的是應該在編譯期間捕獲這些錯誤。

Java provides a mechanism for doing this called the abstract method. This is a method that is incomplete; it has only a declaration and no method body. Here is the syntax for an abstract method declaration:

Java提供了做這種事的機制被稱爲abstract方法,這樣的方法不是完整的;它只有聲明部分沒有方法實現部分。下面給出一個關於abstract方法聲明的例子:

abstract void f();

A class containing abstract methods is called an abstract class. If a class contains one or more abstract methods, the class itself must be qualified as abstract. (Otherwise, the compiler gives you an error message.)

If an abstract class is incomplete, what is the compiler supposed to do when someone tries to make an object of that class? It cannot safely create an object of an abstract class, so you get an error message from the compiler. This way, the compiler ensures the purity of the abstract class, and you don’t need to worry about misusing it.

如果一個類內部有abstract的方法那麼這個類就被稱爲“虛擬類”。如果一個類中創建了一個或者多個虛擬的方法,那麼這個類必須被標識爲虛擬類。否則編譯器會提示錯誤信息。虛擬類是不完整的,但是如果有人試圖創建這個類的對象的時候編譯器會怎麼做呢?去創建虛擬類的對象是不安全的,所以編譯器會提示錯誤信息,這樣編譯器就可以確保這是一個純粹的虛擬類,你就不必擔心誤用這個虛擬類了。

If you inherit from an abstract class and you want to make objects of the new type, you must provide method definitions for all the abstract methods in the base class. If you don’t (and you may choose not to), then the derived class is also abstract, and the compiler will force you to qualify that class with the abstract keyword.

It’s possible to create a class as abstract without including any abstract methods. This is useful when you’ve got a class in which it doesn’t make sense to have any abstract methods, and yet you want to prevent any instances of that class.

如果你繼承了這個虛擬類然後去創建這個新類的對象,那麼你必須在這個新類中實現所有虛擬類中定義的方法,如果沒有全部去實現這些方法,那麼派生類也是虛擬類,並且編譯器也會強制你將這個類標識爲abstract關鍵詞。創建一個不包含虛擬方法的虛擬類也是允許的。如果你希望創建一個不希望被實例話的類,那麼這就可以使用這種方式實現。

The Instrument class can easily be turned into an abstract class. Only some of the methods will be abstract, since making a class abstract doesn’t force you to make all the methods abstract. Here’s what it looks like:

Here’s the orchestra example modified to use abstract classes and methods:

這裏關於orchestra例子被修改爲了使用abstract關鍵詞的類和方法:

package c07.music4;

import com.bruceeckel.simpletest.*;

import java.util.*;

import c07.music.Note;

 

abstract class Instrument {

  private int i;

  public abstract void play(Note n);

  public String what() {

    return "Instrument";

  }

  public abstract void adjust();

}

 

class Wind extends Instrument {

  public void play(Note n) {

    System.out.println("Wind.play() " + n);

  }

  public String what() { return "Wind"; }

  public void adjust() {}

}

 

class Percussion extends Instrument {

  public void play(Note n) {

    System.out.println("Percussion.play() " + n);

  }

  public String what() { return "Percussion"; }

  public void adjust() {}

}

 

class Stringed extends Instrument {

  public void play(Note n) {

    System.out.println("Stringed.play() " + n);

  }

  public String what() { return "Stringed"; }

  public void adjust() {}

}

 

class Brass extends Wind {

  public void play(Note n) {

    System.out.println("Brass.play() " + n);

  }

  public void adjust() {

    System.out.println("Brass.adjust()");

  }

}

 

class Woodwind extends Wind {

  public void play(Note n) {

    System.out.println("Woodwind.play() " + n);

  }

  public String what() { return "Woodwind"; }

}

 

public class Music4 {

  private static Test monitor = new Test();

  static void tune(Instrument i) {

    i.play(Note.MIDDLE_C);

  }

  static void tuneAll(Instrument[] e) {

    for(int i = 0; i < e.length; i++)

      tune(e[i]);

  }

  public static void main(String[] args) {

    Instrument[] orchestra = {

      new Wind(),

      new Percussion(),

      new Stringed(),

      new Brass(),

      new Woodwind()

    };

    tuneAll(orchestra);

    monitor.expect(new String[] {

      "Wind.play() Middle C",

      "Percussion.play() Middle C",

      "Stringed.play() Middle C",

      "Brass.play() Middle C",

      "Woodwind.play() Middle C"

    });

  }

}

You can see that there’s really no change except in the base class.

It’s helpful to create abstract classes and methods because they make the abstractness of a class explicit, and tell both the user and the compiler how it was intended to be used.

你可以看到除了在基類當中並沒有什麼實際的變動。由此可見創建abstract的類和方法是很有用處的,因爲它明確了類的抽象性,並且告訴使用者和編譯器應該如何使用。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章