天天看點

第八章多态

第八章 多态

在面向對象程式設計語言中,多态是繼承資料抽象和繼承之後的第三種基本特征

多态通過 分離做什麼 和 怎麼做 ,從另一個角度将 接口 和 實作 分離開來

  • 改善代碼的組織結構和可讀性
  • 建立可擴充程式 (無論在項目最初建立時還是在需要添加新功能時都可以“生長”程式)

差別

  • 封裝 通過合并特征和行為來建立新的資料類型。
  • 實作隐藏 則通過将細節 私有化 把接口和現實分離開來。
  • 多态 的作用則是消除類型之間的耦合關系。方法調用允許一種類型表現出與其他類型相似類型之間的差別,隻要它們都時從同一個基類導出來的。這種差別時根基方法行為的不同而表示出來的,雖然這些方法都可以通過同一個類型來調用
  • 繼承 允許将對象是為它自己本身的類型或者基類型來加以處理。(允許從基類型導出的視為同一類型處理,而一份代碼也就無差别的允許在這些不同類型上了)

8.1 再論向上轉型

對象既可以作為它自己本身使用,也可以作為它的基類型使用,而這種把某個對象的引用視為對其基類型的引用的做法被稱作向上轉型

8.1.1 忘記對象類型

在進行向上轉型時,就會發生這種情況。我們隻寫一個簡單的方法,它僅接收基類作為參數,而不是那些特殊的導出類。這正是多态所所允許的。

練習1

class Cycle {
    private String name = "Cycle";
    public static void travel(Cycle c) {
        System.out.println("Cycle.ride() " + c);
    }
    public String toString() {
        return this.name;
    }
}

class Unicycle extends Cycle {
    private String name = "Unicycle";
    public String toString() {
        return this.name;
    }
}

class Bicycle extends Cycle {
    private String name = "Bicycle";
    public String toString() {
        return this.name;
    }

}

class Tricycle extends Cycle {
    private String name = "Tricycle";
    public String toString() {
        return this.name;
    }
}

public class No1biking {
    public static void ride(Cycle c) {
        c.travel(c);
    }
    public static void main(String[] args) {
        Unicycle u = new Unicycle();
        Bicycle b = new Bicycle();
        Tricycle t = new Tricycle();
        ride(u);
        ride(b);
        ride(t);
    }
}
============================================================
Cycle.ride() Unicycle
Cycle.ride() Bicycle
Cycle.ride() Tricycle
           

8.2 轉機

第八章多态

8.2.1 方法調用綁定

将一個方法調用同一個方法主體關聯起來被稱作綁定。

若在程式執行前進行綁定(由編譯器和連接配接程式實作) 叫做 前期綁定(面向對象語言 不需要選擇就預設綁定的方式)

運作時根據對象的類型進行綁定,後期綁定 叫做動态綁定或者運作後時綁定。(如果實作後期綁定,就必須具有某種機制,以便在運作時能判斷對象的類型,進而調用恰當的方法,并加以調用。)

Java種 除了 static方法 和 final(private屬于 final方法) 方法,其他所有方法都是 後期綁定 這意味着我們不用 判定是否進行後期綁定 會 自動發生

是以 **為什麼 把 一種方法聲明 為 final 呢 **

  1. 防止其他人覆寫方法
  2. 有效的關閉 動态綁定 告訴編譯器 不需要再對他進行 動态綁定,編譯器就可以為 final方法調用 生成更有效的代碼。

然而 大多數這種情況下 對程式的整體性能 不會由 什麼改變,是以最好根據設計來決定是否使用 finla 而不是試圖提高效率的目的來使用

8.2.2 産生正确的行為

​ 一旦知道 Java中所有方法都時通過 動态綁定實作 多态這個事實之後,我們就可以隻與基類打交道的程式代碼了,并且這些代碼綁定代碼對所有的導出類 都可以 正确運作。或者換一種方法,發生一個消息給某個對象,讓該對象斷定會發生什麼。

​ 經典例子 : 幾何形狀(shape) 直覺 經常用的到

第八章多态

向上轉型:

Shape s = new Circle();

建立一個 Ciecle對象,把它的引用 立即指派給 Shape

假設調用一個基類方法 它已經再導出類裡被覆寫

s.draw();

你可能再次 認為時 Shape 的 draw(), 畢竟是一個 Shape引用,但是時 Circle.draw()方法,怎麼實作的呢? 由于 後期綁定 (多态)

在編譯,編譯器不需要獲得任何特殊資訊就能進行正确的調用。對draw()方法的所有調用都是通過動态綁定進行的

擴充

new Random(47)裡面的47表示産生随機數的一個種子,nextInt(26)表示随機數的範圍,種子和範圍是相關聯的,一個種子對應一個範圍内的一個固定的随機數,如果不填種子,則會預設取目前時間的毫秒數作為種子來生成随機數。

練習二

import java.util.Random;

class Shape {
	public void draw() {}
	public void erase() {}
}

class Circle extends Shape {
	@Override public void draw() {
        System.out.println("Circle.draw()"); }
	@Override public void erase() {
        System.out.println("Circle.erase()"); }
}

class Square extends Shape {
	@Override public void draw() {
        System.out.println("Square.draw()"); }
	@Override public void erase() {
        System.out.println("Square.erase()"); }
}

class Triangle extends Shape {
	@Override public void draw() {
        System.out.println("Triangle.draw()"); }
	@Override public void erase() {
        System.out.println("Triangle.erase()"); }
}

class RandomShapeGenerator {
	private Random rand = new Random(47);
	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 No2Shapes {
    private static RandomShapeGenerator gen = new RandomShapeGenerator();
    public static void main(String[] args) {
        Shape[] s = new Shape[10];
        // fill up the array wth shapes:
        for(int i = 0; i < s.length; i++)
            s[i] = gen.next();
        // make polymorphic method calls:
        for(Shape shp : s)
            shp.draw();
    }
}
==========================================================================
Triangle.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Square.draw()
Triangle.draw()
Circle.draw()
Square.draw()   
           

練習3

a

Triangle.draw()
這裡是基類
Triangle.draw()
這裡是基類
Square.draw()
這裡是基類
Triangle.draw()
這裡是基類
Square.draw()
這裡是基類
Triangle.draw()
這裡是基類
Square.draw()
這裡是基類
Triangle.draw()
這裡是基類
Circle.draw()
這裡是基類
Square.draw()
這裡是基類
           

b

Triangle.draw()
這裡是基類
Triangle.draw()
這裡是基類
Square.draw()
這裡是基類
Triangle.draw()
這裡是基類
Square.draw()
這裡是基類
Triangle.draw()
這裡是基類
Square.draw()
這裡是基類
Triangle.draw()
這裡是基類
Circle.draw()
這裡是Cricle類
Square.draw()
這裡是基類
           

c

Triangle.draw()
這裡是Triangle類
Triangle.draw()
這裡是Triangle類
Square.draw()
這裡是Square類
Triangle.draw()
這裡是Triangle類
Square.draw()
這裡是Square類
Triangle.draw()
這裡是Triangle類
Square.draw()
這裡是Square類
Triangle.draw()
這裡是Triangle類
Circle.draw()
這裡是Cricle類
Square.draw()
這裡是Square類
           

練習5

class Cycle5 {
    private String name = "Cycle5";
    private int wheels = 0;
    public static void travel(Cycle5 c) {
        System.out.println("Cycle5.ride() " + c);
    }
    public int wheels() { return wheels; }
    public String toString() {
        return this.name;
    }
}

class Unicycle5 extends Cycle5 {
    private String name = "Unicycle";
    private int wheels = 1;
    @Override public int wheels() { return wheels; }
    public String toString() {
        return this.name;
    }
}

class Bicycle5 extends Cycle5 {
    private String name = "Bicycle";
    private int wheels = 2;
    @Override public int wheels() { return wheels; }
    public String toString() {
        return this.name;
    }

}

class Tricycle5 extends Cycle5 {
    private String name = "Tricycle";
    private int wheels = 3;
    @Override public int wheels() { return wheels; }
    public String toString() {
        return this.name;
    }
}

public class No5Biking5 {
    public static void ride(Cycle5 c) {
        c.travel(c);
        System.out.println("wheels: " + c.wheels());
    }
    public static void main(String[] args) {
        Unicycle5 u = new Unicycle5();
        Bicycle5 b = new Bicycle5();
        Tricycle5 t = new Tricycle5();
        ride(u); // upcast to Cycle
        ride(b);
        ride(t);
    }
}
===================================================================
Cycle5.ride() Unicycle
wheels: 1
Cycle5.ride() Bicycle
wheels: 2
Cycle5.ride() Tricycle
wheels: 3
           

8.2.3 可擴充性

在 樂器方法 示例中,由于有多态機制。在一個 程式設計良好的 OOP 程式中,大多數或者所有方法都會遵循 tune()的模型 ,根據自己的需求對系統添加任意多的 新類型,而不需要更改 tune() 方法。

大多數方法都會 遵循 tune() 的 模型,而且隻與基類 接口通信。這樣的程式是 可擴充的,因為 可以從通用的基類繼承出新的資料類型,進而新添一些功能。那些操控基類接口的方法不需要任何改動就可以應用于新類。

我們所做的代碼修改,不會對程式中其他不應受到影響的部分産生破壞。

将改變事物與未改變事物分離開來

練習6

enum Note{
    MIDDLE_C,
}

class Instrument {
    void play(Note n) {
        System.out.println("Instrument.play() " + n); }
    public String toString() { return "Instrument"; }
    void adjust() {
        System.out.println("Adjusting Instrument"); }
}

class Wind extends Instrument {
    void play(Note n) {
        System.out.println("Wind.play() " + n); }
    public String toString() { return "Wind"; }
    void adjust() {
        System.out.println("Adjusting Wind"); }
}

class Percussion extends Instrument {
    void play(Note n) {
        System.out.println("Percussion.play() " + n); }
    public String toString() { return "Percussion"; }
    void adjust() {
        System.out.println("Adjusting Percussion"); }
}

class Stringed extends Instrument {
    void play(Note n) {
        System.out.println("Stringed.play() " + n); }
    public String toString() { return "Stringed"; }
    void adjust() {
        System.out.println("Adjusting Stringed"); }
}

class Brass extends Wind {
    void play(Note n) {
        System.out.println("Brass.play() " + n); }
    public String toString() { return "Brass"; }
    void adjust() {
        System.out.println("Adjusting Brass"); }
}

class Woodwind extends Wind {
    void play(Note n) {
        System.out.println("Woodwind.play() " + n); }
    public String toString() { return "Woodwind"; }
}

public class No6Music {
    // Doesn't care about type, so new types
    // added to the system still work right:
    public static void tune(Instrument i) {
        //...
        i.play(Note.MIDDLE_C);
    }
    public static void tuneAll(Instrument[] e) {
        for(Instrument i : e)
            tune(i);
    }
    public static void main(String[] args) {
        // Upcasting during addition to the array:
        Instrument[] orchestra = {
                new Wind(),
                new Percussion(),
                new Stringed(),
                new Brass(),
                new Woodwind()
        };
        tuneAll(orchestra);
        for(Instrument i : orchestra)
            System.out.println(i);
    }
}
==============================================================
Wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C
Wind
Percussion
Stringed
Brass
Woodwind
           

練習7

package 第八章多态;

class Keyboard extends Instrument {
    void play(Note n) {
        System.out.println("Keyboard.play() " + n); }
    public String toString() { return "Keyboard"; }
    void adjust() {
        System.out.println("Adjusting Keyboard"); }
}

class Piano extends Keyboard {
    void play(Note n) {
        System.out.println("Piano.play() " + n); }
    public String toString() { return "Piano"; }
}

public class No7Music {
    // Doesn't care about type, so new types
    // added to the system still work right:
    public static void tune(Instrument i) {
        //...
        i.play(Note.MIDDLE_C);
    }
    public static void tuneAll(Instrument[] e) {
        for(Instrument i : e)
            tune(i);
    }
    public static void main(String[] args) {
        // Upcasting during addition to the array:
        Instrument[] orchestra = {
                new Wind(),
                new Percussion(),
                new Stringed(),
                new Brass(),
                new Woodwind(),
                new Piano()
        };
        tuneAll(orchestra);
        for(Instrument i : orchestra)
            System.out.println(i);
    }
}
========================================================================Wind.play() MIDDLE_C
Percussion.play() MIDDLE_C
Stringed.play() MIDDLE_C
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C
Piano.play() MIDDLE_C
Wind
Percussion
Stringed
Brass
Woodwind
Piano
           

練習8

import java.util.*;
class RandomInstrumentGenerator8 {
    private Random rand = new Random();
    public Instrument next() {
      switch(rand.nextInt(7)) {
         default:
         case 0: return new Wind();
         case 1: return new Percussion();
         case 2: return new Stringed();
         case 3: return new Keyboard();
         case 4: return new Brass();
         case 5: return new Woodwind();
         case 6: return new Piano();
      }
   }
}

public class No8Music {
    // Doesn't care about type, so new types
    // added to the system still work right:
    public static void tune(Instrument i) {
        //...
        i.play(Note.MIDDLE_C);
    }
    public static void tuneAll(Instrument[] e) {
        for(Instrument i : e)
            tune(i);
    }
    private static RandomInstrumentGenerator8 gen = new RandomInstrumentGenerator8();
    public static void main(String[] args) {
        // Upcasting during addition to the array:
        Instrument[] orchestra = new Instrument[5];
        // fill up orchestra array wth instruments:
        for(int i = 0; i < orchestra.length; i++)
            orchestra[i] = gen.next();
        tuneAll(orchestra);
        for(Instrument i : orchestra)
            System.out.println(i);
    }
}
=================================================================
Brass.play() MIDDLE_C
Woodwind.play() MIDDLE_C
Keyboard.play() MIDDLE_C
Brass.play() MIDDLE_C
Piano.play() MIDDLE_C
Brass
Woodwind
Keyboard
Brass
Piano
           

練習9

import java.util.Random;

class RandomRodentGenerator9 {
   private Random rand = new Random();
   public Rodent next() {
      switch(rand.nextInt(3)) {
         default:
         case 0: return new Mouse();
         case 1: return new Rat();
         case 2: return new Squirrel();
      }
   }
}

class Rodent {
    private String name = "Rodent";
    protected void eat() {
        System.out.println("Rodent.eat()"); }
    protected void run() {
        System.out.println("Rodent.run()"); }
    protected void sleep() {
        System.out.println("Rodent.sleep()"); }
    public String toString() { return name; }
}

class Mouse extends Rodent {
    private String name = "Mouse";
    protected void eat() {
        System.out.println("Mouse.eat()"); }
    protected void run() {
        System.out.println("Mouse.run()"); }
    protected void sleep() {
        System.out.println("Mouse.sleep()"); }
    public String toString() { return name; }
}

class Rat extends Rodent {
    private String name = "Rat";
    protected void eat() {
        System.out.println("Rat.eat()"); }
    protected void run() {
        System.out.println("Rat.run()"); }
    protected void sleep() {
        System.out.println("Rat.sleep()"); }
    public String toString() { return name; }
}

class Squirrel extends Rodent {
    private String name = "Squirrel";
    protected void eat() {
        System.out.println("Squirrel.eat()"); }
    protected void run() {
        System.out.println("Squirrel.run()"); }
    protected void sleep() {
        System.out.println("Squirrel.sleep()"); }
    public String toString() { return name; }
}

public class No9Rodent {
    private static RandomRodentGenerator9 gen = new RandomRodentGenerator9();
    public static void main(String[] args) {
        Rodent[] rodents = new Rodent[5];
        for(Rodent r : rodents) {
            r = gen.next();
            System.out.println(r + ": ");
            r.eat();
            r.run();
            r.sleep();
        }
    }
}
==============================================================================
Squirrel: 
Squirrel.eat()
Squirrel.run()
Squirrel.sleep()
Rat: 
Rat.eat()
Rat.run()
Rat.sleep()
Rat: 
Rat.eat()
Rat.run()
Rat.sleep()
Mouse: 
Mouse.eat()
Mouse.run()
Mouse.sleep()
Rat: 
Rat.eat()
Rat.run()
Rat.sleep()
           

加 Override 與 不加 Override 的 差別

java 中子類繼承基類,可以重寫基類中的方法,一般我們會定義 @Override 注解。

那麼是否可以不加 @Override 呢?顯然也可以!

那麼,對于java中添加 Override 的差別在哪裡呢?

寫的情況下,表示子類覆寫基類中的方法,基類中必須存在該方法,控制器類型(public,protected,傳回值,參數清單)與子類方法完全一緻,否則會報錯(找不到被 Override 的方法)

不寫的情況下,JVM也會自動識别,如果不滿足完全一緻的條件,則被當作新方法定義

加Override的好處有利于編譯器幫忙檢測錯誤

練習10

class A {
    protected void f() {
        System.out.println("A.f()");
        this.g();
    }
    protected void g() {
        System.out.println("A.g()");
    }
}

class B extends A {
    @Override protected void g() {
        System.out.println("B.g()");
    }
}


public class No10Ex {
    public static void main(String[] args) {
        B b = new B();
        // automatic upgrade to base-class to call base-class method A.f()
        // which,by polymorphism, will call the derived-class method B.g():
        b.f();
    }

}
=======================================================================
A.f()
B.g()
           

8.2.4 缺陷: “覆寫”私有方法

Private 方法被自動認為使 final 方法,進而對導出類是屏蔽的。是以 導出類 如果覆寫 基類中的

private方法 那麼其實并沒有,會建立一個新方法。是以為了消除這種歧義,在導出類中最好不要與 基類中的 private 方法同名。

8.2.5 缺陷:“域于靜态方法“

隻有普通的方法調用可以是多态的

直接通路 域 ,這個通路就在編譯期間進行解析,任何域通路 操作都将由編譯器編譯,是以不是多态的。

為 Super.field 和 sub.field 配置設定了不同的存儲空間。

但 實際上 從來不會發生,**首先,你通常會将所有的域都設定成private ,是以不能之間通路它們,其副作用是隻能調用方法通路 **

靜态方法,它的行為就不具有多态性:

第八章多态

8.3 構造器和多态

​ 構造器并沒有多态(隐式的static方法),但是應該要了解構造器怎麼通過多态在複雜的層次結構中運作

8.3.1 構造器的調用順序

構造器 有一項特殊的任務:

檢查對象是否被正确地構造。導出類隻能通路它自己的成員,不能通路基類成員(基類成員通常是private類型)。隻有基類的構造器才具有恰當的知識和權限來對自己的元素進行初始化。是以必須令所有的構造器都必須調用,否則不肯正确構造完整對象

  • 調用基類構造器 ,從根開始 直到最底層
  • 按聲明順序調用成員的初始化方法
  • 調用導出類構造器主體

在構造器内部 我們必須確定所要使用的基類的所有成員都以建構完成,為確定這一目的,唯一的方法就是必須調用基類構造器。那麼在進入導出類構造器是,在基類中可供我們通路的成員都已得到了初始化。

通過組合方法将對象置于類内,也應初始化目前對象的成員對象。(調用構造器,但這種方法不适用于所有情況)

練習11

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 Pickle {
    Pickle() {
        System.out.println("Pickle()"); }
}

class Lunch extends Meal {
    Lunch() {
        System.out.println("Lunch()"); }
}

class PortableLunch extends Lunch {
    PortableLunch() {
        System.out.println("PortableLunch()"); }
}

public class No11Sandwich11 extends PortableLunch {
    private Bread b = new Bread();
    private Cheese c = new Cheese();
    private Pickle p = new Pickle();
    private Lunch l = new Lunch();
    public No11Sandwich11() {
        System.out.println("Sandwich()"); }
    public static void main(String[] args) {
        new No11Sandwich11();
    }
}
====================================================================
Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Pickle()
Meal()
Lunch()
Sandwich()
           

8.3.2 繼承和清理

第八章多态

層次結構 中 的每個類都包含 Characteristic 和 Description 這 兩種類型的成員對象,并且它們必須被銷毀。是以萬一某個子對象 要 依賴于其他對象,銷毀的順序應該和初始化順序相反。先銷魂導出類,再基類,因為基類的方法再導出類沒有銷毀之前可能還會被調用。

我們通常不必執行清理動作,但是一旦選擇要執行,就必須謹慎和小心

練習12

import java.util.Random;

class RandomRodentGenerator12 {
   private Random rand = new Random();
   public Rodent12 next() {
      switch(rand.nextInt(3)) {
         default:
         case 0: return new Mouse12();
         case 1: return new Rat12();
         case 2: return new Squirrel12();
      }
   }
}

class Characteristic {
    private String s;
    Characteristic(String s) {
        this.s = s;
        System.out.println("Creating Characteristic " + s);
    }
}

class Description {
    private String s;
    Description(String s) {
        this.s = s;
        System.out.println("Creating Description " + s);
    }
}

class Rodent12 {
    private String name = "Rodent";
    private Characteristic c = new Characteristic("has tail");
    private Description d = new Description("small mammal");
    Rodent12() {
        System.out.println("Rodent()"); }
    protected void eat() {
        System.out.println("Rodent.eat()"); }
    protected void run() {
        System.out.println("Rodent.run()"); }
    protected void sleep() {
        System.out.println("Rodent.sleep()"); }
    public String toString() { return name; }
}

class Mouse12 extends Rodent12 {
    private String name = "Mouse";
    private Characteristic c = new Characteristic("likes cheese");
    private Description d = new Description("nocturnal");
    Mouse12() {
        System.out.println("Mouse()"); }
    protected void eat() {
        System.out.println("Mouse.eat()"); }
    protected void run() {
        System.out.println("Mouse.run()"); }
    protected void sleep() {
        System.out.println("Mouse.sleep()"); }
    public String toString() { return name; }
}

class Rat12 extends Rodent12 {
    private String name = "Rat";
    private Characteristic c = new Characteristic("larger");
    private Description d = new Description("black");
    Rat12() {
        System.out.println("Rat()"); }
    protected void eat() {
        System.out.println("Rat.eat()"); }
    protected void run() {
        System.out.println("Rat.run()"); }
    protected void sleep() {
        System.out.println("Rat.sleep()"); }
    public String toString() { return name; }
}

class Squirrel12 extends Rodent12 {
    private String name = "Squirrel";
    private Characteristic c = new Characteristic("climbs trees");
    private Description d = new Description("likes nuts");
    Squirrel12() {
        System.out.println("Squirrel()"); }
    protected void eat() {
        System.out.println("Squirrel.eat()"); }
    protected void run() {
        System.out.println("Squirrel.run()"); }
    protected void sleep() {
        System.out.println("Squirrel.sleep()"); }
    public String toString() { return name; }
}

public class No12Rodent {
    private static RandomRodentGenerator12 gen = new RandomRodentGenerator12();
    public static void main(String[] args) {
        Rodent12[] rodents12 = new Rodent12[3];
        for(Rodent12 r : rodents12) {
            r = gen.next();
            System.out.println(r);
        }
    }
}
===============================================================
Creating Characteristic has tail
Creating Description small mammal
Rodent()
Creating Characteristic larger
Creating Description black
Rat()
Rat
Creating Characteristic has tail
Creating Description small mammal
Rodent()
Creating Characteristic climbs trees
Creating Description likes nuts
Squirrel()
Squirrel
Creating Characteristic has tail
Creating Description small mammal
Rodent()
Creating Characteristic likes cheese
Creating Description nocturnal
Mouse()
Mouse
           

存在于其他一個或多個對象共情的情況,問題就變得更加複雜了,你就不可能簡單的調用dispose()了。這種情況下必須使用 引用計數器 來 追蹤仍舊通路着共享對象得數量了。

練習13

class Shared {
	private int refcount = 0; //判斷   會逐漸增大 也會減小
	private static long counter = 0;// 計數   隻能增加
	private final long id = counter++;
	public Shared() {
		print("Creating " + this);
	}
	public void addRef() { refcount++; }
	protected void finalize() {
		if(refcount > 0)
			print("Error: " + refcount + " Shared " + id + " objects in use");
	}
	protected void dispose() {
		if(--refcount == 0)
			print("Disposing " + this);
	}
	public String toString() { return "Shared " + id; }
}

class Composing {
	private Shared shared;
	private static long counter = 0;
	private final long id = counter++;
	public Composing(Shared shared) {
		print("Creating " + this);
		this.shared = shared;
		this.shared.addRef();
	}
	protected void dispose() {
		print("Disposing " + this);
		shared.dispose();
	}
	public String toString() { return "Composing " + id; }
}

public class ReferenceCounting13 {
	public static void main(String[] args) {
		Shared shared = new Shared();
		Composing[] composing = { new Composing(shared),
			new Composing(shared), new Composing(shared),
			new Composing(shared), new Composing(shared)			
		};
		for(Composing c : composing)
			c.dispose();
		Composing compTest = new Composing(shared);
		Composing compTest2 = new Composing(shared);
		// Test finalize():
		shared.finalize();
		Shared sharedTest = new Shared();
		Composing compTest3 = new Composing(sharedTest);
		// Test sharedTest finalize():
		sharedTest.finalize();
	}
}
===================================================================
Creating Shared 0
Creating Composing 0
Creating Composing 1
Creating Composing 2
Creating Composing 3
Creating Composing 4
Disposing Composing 0
Disposing Composing 1
Disposing Composing 2
Disposing Composing 3
Disposing Composing 4
Disposing Shared 0
Creating Composing 5
Creating Composing 6
Error: 2 Shared 0 objects in use
Creating Shared 1
Creating Composing 7
Creating Composing 8
Error: 2 Shared 1 objects in use
           

8.3.3 構造器内部的多态方法的行為

如果 一個構造器的内部 調用 正在構造器對象 的某個動态綁定 方法,那會發生什麼?

在一般方法的内部 , 動态綁定的調用時在運作時才決定的,因為對象 無法知道 它是屬于方法所在的類,還是那個類的導出類。

第八章多态

初始化的實際過程時:

  • 在其他任何事物之前,将配置設定給對象的存儲空間初始化成二進制零
  • 如前述那樣調用基類構造器。此時,調用覆寫後的draw()方法(要在調用RoundGlyph構造器之前調用),由于步驟1的緣故,我們此時會發現radius值為0.
  • 按照聲明的順序調用成員的初始化方法
  • 調用導出類的構造器主體
  • 編寫構造器時 有一條有效的準則:”用盡可能簡單地方法使對象進入正常狀态;如果可以地話,避免調用其他方法(能夠安全調用地 final 方法(也适用于private方法))

**覆寫和重載的差別 **

①重載是指不同的函數bai使用相同的函數名,但是函數的參數個數或類型不同。調用的時候根據函數的參數來差別不同的函數。

②覆寫(也叫重寫)是指在派生類中重新對基類中的虛函數(注意是虛函數)重新實作。即函數名和參數都一樣,隻是函數的實作體不一樣。

2.類的關系差別

覆寫是子類和父類之間的關系,是垂直關系;重載是同一個類中方法之間的關系,是水準關系。

3.産生方法差別

覆寫隻能由一個方法或隻能由一對方法産生關系;重載是多個方法之間的關系。

練習15

class Glyph {
    void draw() {
        System.out.println("Glyph.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);
    }//覆寫方法
}

class RectangularGlyph extends Glyph {
    private int length = 2;
    private int width = 4;
    RectangularGlyph(int l, int w) {
        length = l;
        width = w;
        System.out.println("RectangularGlyph.RectangularGlyph(), length = "
                + length + ", width = " + width);
    }
    void draw() {
        System.out.println("RectangularGlyph.draw(), length = " + length
                + ", width = " + width);
    }
}


public class No15PolyConstructors {
    public static void main(String[] args) {
        new RoundGlyph(5);
        new RectangularGlyph(3, 6);
    }
}
==========================================================================
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
Glyph() before draw()
RectangularGlyph.draw(), length = 0, width = 0
Glyph() after draw()
RectangularGlyph.RectangularGlyph(), length = 3, width = 6
           

8.4 協變傳回類型

​ 協變傳回類型,它表示在導出類中的被覆寫方法可以傳回基類方法的傳回類型的某種導出類型:

第八章多态
  • WhearMill 是導出類
  • process 是覆寫方法
  • Mill 是基類方法
  • new Grain 是基類方法的傳回類型
  • Wheat() 是基類方法的一種導出類型

8.5 用繼承進行設計

因為多态是一種如此巧妙地工具。當我們使用現成的類來建立新類時,如果首先考慮使用繼承計數,反倒會加重我們地設計負擔。

更好地方式是:

首先選擇 “組合”,尤其時不能确定應該使用哪一種方式時。組合不會強制我們的程式設計進入繼承的層次結構中。而且更加靈活,因為它可以動态的選擇類型(是以也選擇了行為),相反,繼承在編譯時就需要知道确切類型。

第八章多态

用繼承表達行為間的差異,用字段表達狀态上的變化

練習16

class AlertStatus {
    public void alert() {}
}

class NormalAlert extends AlertStatus {
    public void alert() {
        System.out.println("AlertStatus Normal"); }
}

class HighAlert extends AlertStatus {
    public void alert() {
        System.out.println("AlertStatus High"); }
}

class MaximumAlert extends AlertStatus {
    public void alert() {
        System.out.println("AlertStatus Maximum"); }
}

class Starship {
    private AlertStatus alertStatus = new NormalAlert();
    public void normalAlert() { alertStatus = new NormalAlert(); }
    public void highAlert() { alertStatus = new HighAlert(); }
    public void maximumAlert() { alertStatus = new MaximumAlert(); }
    public void checkAlertStatus() { alertStatus.alert(); }
}

public class No16Transmogrify {
    public static void main(String[] args) {
        Starship ss = new Starship();
        ss.checkAlertStatus();
        ss.highAlert();
        ss.checkAlertStatus();
        ss.maximumAlert();
        ss.checkAlertStatus();
        ss.normalAlert();
        ss.checkAlertStatus();
    }
}
========================================================================
AlertStatus Normal
AlertStatus High
AlertStatus Maximum
AlertStatus Normal
           

8.5.1 純繼承與擴充

純粹的方式來建立繼承層次結構似乎是最好的方式。隻有在基類中已經建立的方法才可以在導出類中被覆寫

第八章多态

基類可以接收發送消息給導出類的任何消息,因為二者有着完全相同的接口。我們隻需要從導出類向上轉型,都是通過多态來處理的。

導出類中的接口的擴充部分不能被基類通路,是以,一旦我們向上轉型,就不能調用那些新的方法

8.5.2 向下轉型與運作時類型識别

由于向上轉型會丢失具體的 類型資訊 是安全的。這種在運作期間對類型進行檢查的行為稱為 “運作時類别識别RTTI(Runtime Type Identification RTTI)”

**向下轉型 應該就能獲得類型資訊?不安全,貿然轉移到一種錯誤類型 **

第八章多态

想通路MoreUseful對象的擴充接口,向下轉型是正确的類型,那麼轉型成功,否則 抛出 ClassCastException異常。

練習17

package 第八章多态;

class Cycle {
    private String name = "Cycle";
    public static void travel(Cycle c) {
        System.out.println("Cycle.ride() " + c);
    }
    public String toString() {
        return this.name;
    }
}

class Unicycle extends Cycle {
    private String name = "Unicycle";
    public void balance() {
        System.out.println("Balance Unicycle"); }
    public String toString() {
        return this.name;
    }
}

class Bicycle extends Cycle {
    private String name = "Bicycle";
    public void balance() {
        System.out.println("Balance Bicycle"); }
    public String toString() {
        return this.name;
    }

}

class Tricycle extends Cycle {
    private String name = "Tricycle";
    public String toString() {
        return this.name;
    }
}

public class No17Biking {
    public static void main(String[] args) {
        Cycle[] ride = {
                new Unicycle(),
                new Bicycle(),
                new Tricycle(),
        };
        // Compile time error: cannot find balance() method in Cycle:
        // for(Cycle c : ride) {
        // c.balance();
        // }
        ((Unicycle)ride[0]).balance();
        ((Bicycle)ride[1]).balance();
        // Compile time error: no balance() in Tricycle: // 不能随便轉 還得有這個類型
        // ((Tricycle)ride[2]).balance();
        // RTTI: ClassCastException: Tricycle cannot be cast to Bicycle:
        // ((Bicycle)ride[2]).balance();
    }
}
===============================================================
Balance Unicycle
Balance Bicycle
           

8.6 總結

第八章多态