1 面向對象的三個原則 封裝性 封裝的基本單元是類(class),類是一個抽象的邏輯結構,而類的對象是一個真實的物理實體;類的目的是封裝復雜性,在類內部存在隱藏實現復雜性機制; 封裝(encapsulation) 的兩個好處: 模塊化:可以獨立於其他對象的源代碼進行編寫和維護,可以很容易的將對象在系統中傳遞; 隱藏信息:其他對象可以通過本對象的一個公共接口進行通信而不影響其他對象; 繼承性 繼承是一個對象獲得另一個對象的屬性的過程,繼承機制是一個對象成為一個更具通用類的一個特定實例成為可能,避免了代碼的重復編寫; 多態性 (重載overload,方法名相同、參數的個數不同、參數的類型不同、返回的類型不同和覆蓋override) ;多態性就是“一種接口,多種方法”,可以為一組相關的動作設計一個通用的接口,其實類的函數的重載就是一種多態的體現; 4 引入抽象編程的思想; 類的封裝就是一種抽象思想
Java中除了static方法和final方法(private方法本質上屬於final方法,因為不能被子類訪問)之外,其它所有的方法都是動態綁定,這意味著通常情況下,我們不必判定是否應該進行動態綁定—它會自動發生。
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(); System.out.println(sup.staticGet()); System.out.println(sup.dynamicGet()); } }
Base staticGet()
Derived dynamicGet()
構造函數並不具有多態性,它們實際上是static方法,只不過該static聲明是隱式的。因此,構造函數不能夠被override。
在父類構造函數內部調用具有多態行為的函數將導致無法預測的結果,因為此時子類對象還沒初始化,此時調用子類方法不會得到我們想要的結果。
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); } } public class PolyConstructors { public static void main(String[] args) { new RoundGlyph(5); } }
輸出:
Glyph() before draw()
RoundGlyph.draw(). radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(). radius = 5
為什麼會這樣輸出?這就要明確掌握Java中構造函數的調用順序:
(1)在其他任何事物發生之前,將分配給對象的存儲空間初始化成二進制0;
(2)調用基類構造函數。從根開始遞歸下去,因為多態性此時調用子類覆蓋後的draw()方法(要在調用RoundGlyph構造函數之前調用),由於步驟1的緣故,我們此時會發現radius的值為0;
(3)按聲明順序調用成員的初始化方法;
(4)最後調用子類的構造函數。
只有非private方法才可以被覆蓋,但是還需要密切注意覆蓋private方法的現象,這時雖然編譯器不會報錯,但是也不會按照我們所期望的來執行,即覆蓋private方法對子類來說是一個新的方法而非重載方法。因此,在子類中,新方法名最好不要與基類的private方法采取同一名字(雖然沒關系,但容易誤解,以為能夠覆蓋基類的private方法)。
Java類中屬性域的訪問操作都由編譯器解析,因此不是多態的。父類和子類的同名屬性都會分配不同的存儲空間,如下:
// Direct field access is determined at compile time. class Super { public int field = 0; public int getField() { return field; } } class Sub extends Super { public int field = 1; public int getField() { return field; } public int getSuperField() { return super.field; } } public class FieldAccess { public static void main(String[] args) { Super sup = new Sub(); System.out.println("sup.filed = " + sup.field + ", sup.getField() = " + sup.getField()); Sub sub = new Sub(); System.out.println("sub.filed = " + sub.field + ", sub.getField() = " + sub.getField() + ", sub.getSuperField() = " + sub.getSuperField()); } }
輸出:
sup.filed = 0, sup.getField() = 1
sub.filed = 1, sub.getField() = 1, sub.getSuperField() = 0
Sub子類實際上包含了兩個稱為field的域,然而在引用Sub中的field時所產生的默認域並非Super版本的field域,因此為了得到Super.field,必須顯式地指明super.field。