多態, 抽象, 繼承, 這三種基本類型特徵。
多態通過分離做什麼,怎麼做, 就是 講接口和接口的具體實現分離開來。
在Java類中使用super來調用父類中的指定操作: super可用於訪問父類中定義的屬性 super可用於調用父類中定義的成員方法 super可用於在子類構造方法中調用父類的構造器 注意: 尤其當子父類出現同名成員時,可以用super進行區分 super的追溯不僅限於直接父類 super和this的用法相像,this代表本類對象的引用,super代表父類的內存空間的標識
1、8.1 向上轉型的由來
先看下代碼演化:
class Instrument {
public void play(Note n) {
System.out.println("Instrument.play()");
}
}
public class Wind extends Instrument {
// Redefine interface method:子類重寫了父類play的方法,後期調用都是子類的實現,除非調用super來調用父類的paly
public void play(Note n) {
System.out.println("Wind.play() " + n);
}
}
public class Music {
public static void tune(Instrument i) {
// ...
i.play(Note.MIDDLE_C);//Instrument 裏傳遞的參數是它的子類(wind),
}
public static void main(String[] args) {
Wind flute = new Wind();//構造空對象。
tune(flute); // Upcasting
}
}
class Brass extends Instrument {
public void play(Note n) {
System.out.println("Brass.play() " + n);
}
}
8.2.1 方法調用綁定
前期綁定:將一個方法調用同一個方法主體關聯起來被稱作綁定,若在程序執行之前進行綁定。
後期綁定:在運行期間 根據對象的類型進行綁定。亦可以稱爲動態綁定或運行時候綁定。
如果一種語言實現後期綁定,就必須具有某種機制,一遍在運行時候判斷對象的類型,也就是說,編譯器一直不知道對象的類型,但是方法調用機制能找到正確的方法體,加以調用。
java代碼中除了static和final (private方法屬於final方法) 之外,其他所有的方法都是後期綁定。
public class PrivateOverride {
private void f() { System.out.println("private f()"); }
public static void main(String[] args) {
PrivateOverride po = new Derived();
po.f();
}
}
class Derived extends PrivateOverride {
public void f() { System.out.println("public f()"); }
}
實際結果調用的是父類的f()方法,也就是說 父類的private是不能覆蓋的,
8.2.5域和靜態方法
class StaticSuper {
public static String staticGet() {
return "Base staticGet()";
}
public String dynamicGet() {
return "Base dynamicGet()";
}
}
class StaticSub extends StaticSuper {
public static String staticGet() {
return "Derived staticGet()";
}
public String dynamicGet() {
return "Derived dynamicGet()";
}
}
public class StaticPolymorphism {
public static void main(String[] args) {
StaticSuper sup = new StaticSub(); // Upcast
System.out.println(sup.staticGet());//調用父類的靜態方法,
System.out.println(sup.dynamicGet());//因爲子類重寫了父類方法,所以調用的是子類的方法
}
}
靜態方法是與類,而非與單個單向相關聯。
8.3 構造器和多態
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 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();
}
}
解析:1)優先調用父類構造器,不斷的反覆下去,一直到構造這種層次的根,也就是最底層的類,然後在調用子類。
2)按照聲明順序調用成員的初始化方法。
3)最後調用當前類的構造器主體。
這樣達到所有的成員對象都被初始化。
8.3.2繼承和初始化
若對象需要清理的時候,則,子類必須要重寫父類方法,且在重寫的方法重調用父類的dispose方法(super調用),否則父類清理不會發生。銷魂的順序和創建的順序想法,優先銷燬子類的方法,從裏向外擴展。成員變量也是倒推銷燬。
8.3.3構造器內部的多態方法的行爲
class Glyph {
void draw() { System.out.println("Glyph.draw()"); }
Glyph() {
System.out.println("Glyph() before draw()");
draw();//① 因爲子類重寫了darw ,所以調用子類方法
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);//②子類的初始值還未加載,所以第一次radius當前爲0,
}
}
public class PolyConstructors {
public static void main(String[] args) {
new RoundGlyph(5);//優先加載父類構造器
}
}
初始化實際過程:
1)在其他事物發生之前,將分配給對象的存儲空間初始化城二進制的零
2)優先調用父類構造器,到根部,
3)按照聲明的順序調用成員的初始化方法。
4)調用導出類的構造器主體
8.5用繼承進行設計
class Actor {
public 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 {
public static void main(String[] args) {
Stage stage = new Stage();
stage.performPlay();
stage.change();
stage.performPlay();
}
}
第一調用performplay 爲happactor的對象,第二次給改變了,就爲sadactor對象了,
對象引用在運行的時候可以與另一個不同的對象重新綁定起來,
8.5.2向下轉型和運行時類別判定
class Useful {
public void f() {
System.out.println("userful f");
}
public void g() { System.out.println("userful g");}
}
class MoreUseful extends Useful {
public void f() {
System.out.println( "mode f");
}
public void g() { System.out.println( "mode 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();
// Compile time: method not found in Useful:
//! x[1].u();
((MoreUseful)x[1]).u(); // Downcast/RTTI
((MoreUseful)x[0]).u(); // Exception thrown 報錯
}
}
分類在轉爲子類的時候,會發生異常