Java 面向對象2 構造器 繼承 多態性 初始化塊

深刻構造器

構造器是一個特殊的方法,用於建立實例時執行初始化。java

使用構造器執行

當建立一個對象時,系統爲這個對象的實例變量進行默認初始化,這種默認的初始化把全部基本類型的實例變量設爲0或false,把全部引用類型的實例變量設爲Null。程序員

若是程序員沒有爲Java類提供任何構造器,則系統會爲這個類提供一個無參數的構造器,這個構造器的執行體爲空,不作任何事情。不管如何,Java類至少包含一個構造器。數組

一般把構造器設置爲public訪問權限,從而容許系統中任何位置的類來建立該類的對象。ide

構造器重載

同一個類裏具備多個構造器,多個構造器的形參列表不一樣,即被稱爲構造器重載,構造器重載容許Java類裏包含多個初始化邏輯,從而容許使用不一樣的構造器來初始化Java對象。工具

類的繼承

繼承的特色

Java的繼承經過extends關鍵字來實現,實現繼承的類被稱爲子類,被繼承的類被稱爲父類。父類與子類的關係,是一種通常和特殊的關係。Java類只能有一個直接父類。this

修飾符    class    SubClass    extends    SuperClass
{
    //類定義部分
}

java.lang.Object類是全部類的父類,要麼是直接父類,要麼是其間接父類。所以全部的java對象均可以調用java.lang.Object類定義的實例方法。設計

重寫父類的方法

子類包含與父類同名方法你的現象被稱爲方法重寫(Override),也稱爲方法覆蓋。指針

方法的重寫遵循「兩同兩小一大」規則code

  • 「兩同」:方法名、形參列表相同;對象

  • 「兩小」:子類方法返回值類型應比父類返回值類型更小或相等,子類方法聲明拋出的異常類應比父類方法聲明拋出的異常類更小或相等。

  • 「一大」:子類方法的訪問權限應比父類方法的訪問權限更大或相等。

覆蓋方法和被覆蓋方法要麼都是類方法,要麼都是實例方法,不能一個類方法,一個是實例方法。

當子類覆蓋了父類方法後,子類的對象將沒法訪問父類中被覆蓋的方法,但能夠在子類方法中調用父類中被覆蓋的方法。若是須要在子類方法最後調用父類中被覆蓋的方法,則可使用super(被覆蓋的是實例方法)或者父類類名(被覆蓋的是類方法)做爲調用者來調用父類中被覆蓋的方法。

若是父類方法具備private訪問權限,則該方法對其子類是隱藏的,所以其子類沒法訪問該方法,也就是沒法重寫該方法。若是子類中定義了一個與父類private方法具備相同的方法名、相同的形參列表、相同的返回值類型的方法,依然不是重寫,只是在子類中從新定義了一個新方法。

super限定

若是須要在子類方法中調用父類被覆蓋的實例方法,則可以使用super限定來調用父類被覆蓋的實例方法。super用於限定該對象調用它從父類繼承獲得的實例變量或方法。this和super均不能出如今static修飾的方法中。

若是在某個方法中訪問名爲a的成員變量,但沒有顯式指定調用者,則系統查找a的順序爲:

  1. 查找該方法中是否有名爲a的局部變量。

  2. 查找當前類中是否包括名爲a的成員變量。

  3. 查找a的直接父類中是否包含名爲a的成員變量,依次上溯a的全部父類,直到java.lang.Object類,若是最終不能找到名爲a的成員變量,則系統出現編譯錯誤。

若是被覆蓋的是類變量,在子類的方法中則能夠經過父類名做爲調用者來訪問被覆蓋的類變量。

當程序建立一個子類對象時,系統不只會爲該類中定義的實例變量分配內存,也會爲它從父類繼承獲得的全部實例變量分配內存,即便子類定義了與父類中同名的實例變量。

調用父類構造器

在一個構造器中調用另外一個重載的構造器使用this來完成,在子類構造器使用super調用來完成。this和super調用構造器必須出如今構造器執行體的第一行。

子類構造器調用父類構造器分以下幾種狀況:

  1. 子類構造器執行體的第一行使用super顯式調用父類構造器,系統將根據super調用裏傳入的實參列表調用父類對應的構造器。

  2. 子類構造器執行體的第一行代碼使用this顯式調用本類中重載構造器,系統將根據this調用裏傳入的實參列表調用本類中的另外一個構造器。執行本類中另外一個構造器時即會調用父類構造器。

  3. 子類構造器執行體中既沒有super調用,也沒有this調用,系統將會在執行子類構造器以前,隱式調用父類無參數的構造器。

建立任何java對象,最早執行的老是java.lang.Object類的構造器。即,建立任何對象老是從該類所在繼承樹最頂層類的構造器開始執行,而後依次向下執行,最後才執行本類的構造器。若是某個父類經過this調用了同類中重載的構造器,就會依次執行此父類的多個構造器。

多態

多態性

Java引用變量有兩個類型:編譯時類型,運行時類型。

編譯時類型由聲明該變量時使用的類型決定,運行時類型由實際賦給該變量的對象決定。若是編譯時類型和運行時類型不一致,就可能出現所謂的多態。

BaseClass bc = new BaseClass();
SubClass sc = new SubClass();
BaseClass polymorphicBc = new SubClass();

上面程序顯式建立了三個引用變量,對於前兩個引用變量bc和sc,它們編譯時類型和運行時類型徹底相同,所以調用它們的成員變量和方法很是正常,徹底沒有任何問題。但第三個引用變量polymorphic則比較特殊,它的編譯時類型是BaseClass,而運行時類型是SubClass,當調用該引用變量的test()方法(BaseClass類中定義了該方法,子類SubClass覆蓋了父類的該方法)時,實際執行的是SubClass類中覆蓋後的test()方法,這就可能出現多態了。

由於子類實際上是一種特殊的父類,所以Java容許把一個子類對象直接賦給一個父類引用變量,無須任何類型轉換,或者被稱爲向上轉型(upcasting),向上轉型由系統自動完成。

相同類型的變量、調用同一方法時呈現出多種不一樣的行爲特徵,這就是多態。

polymorphicBc.sub();這行代碼會在編譯時引起錯誤。雖然polymorphicBc引用變量實際上確實包含sub()方法,但由於它的編譯時類型爲BaseClass,所以編譯時沒法調用sub方法。

與方法不一樣的是,對象的實例變量則不具有多態性。好比上面的polymorphicBc引用變量,程序中輸出它的book實例變量時,並非輸出SubClass類裏定義的實例變量,而是輸出BaseClass類的實例變量。

引用變量在編譯階段只能調用其編譯時類型所具備的方法,但運行時則執行它運行時類型所具備的方法。所以,編寫Java代碼時,引用變量只能調用聲明該變量時所用類裏包含的方法。例如,經過Object p = new Person()代碼定義了一個變量p,則這個p只能調用Object類的方法,而不能調用Person類裏定義的方法。

經過引用變量來訪問其包含的實例變量時,系統老是試圖訪問它編譯時類型所定義的成員變量,而不是它運行時類型所定義的成員變量。

引用變量的強制類型轉換

編寫Java程序時,引用變量只能調用它編譯時類型的方法,而不能調用它運行時類型的方法,即便它實際所引用的對象倒是包含該方法。若是須要讓這個引用變量調用它運行時類型的方法,則必須把它強制類型轉換成運行時類型,強制類型轉換須要藉助於類型轉換運算符。

當進行強制類型轉換時須要注意:

  • 基本類型之間的轉換隻能在數值類型之間進行,這裏所說的數組類型包括整數性、字符型和浮點型。但數值型和布爾類型直接不能進行類型轉換。

  • 引用類型直接的轉換隻能在具備繼承關係的兩個類型之間進行,若是是兩個沒有任何繼承關係的類型,則沒法進行類型轉換,不然編譯時就會出現錯誤。若是試圖把一個父類實例轉換成子類實例,則這個對象必須其實是子類實例才行(即編譯時類型爲福類型,而運行時類型是子類類型),不然將在運行時引起ClassCastException異常。

    進行強制類型轉換時可能出現異常,所以進行類型轉換以前應先經過instanceof運算符來判斷是否能夠成功轉換,從而避免出現ClassCastException異常,這樣能夠保證程序更加健壯。

instanceof運算符

instanceof運算符的前一個操做數一般是一個引用類型變量,後一個操做數一般是一個類(也能夠是接口),它用於判斷前面的對象是不是後面的類,或者其子類、實現類的實例。若是是返回true,不然返回false。

instanceof運算符前面操做數的編譯時類型要麼與後面的類相同,要麼與後面的類具備父子繼承關係,不然會引發編譯錯誤。

instanceof和(type)是Java提供的兩個相關的運算符,一般先用instanceof判斷一個對象是否能夠強制類型轉換,而後再使用(type)運算符進行強制類型轉換,從而保證程序不會出現錯誤。

繼承與組合

使用繼承的注意點

爲了保證父類有良好的封裝性,不會被子類隨意改變,設計父類一般應該遵循以下規則。

  1. 儘可能隱藏父類的內部結構。儘可能把父類的全部變量都設置成private訪問類型,不要讓子類直接訪問父類的成員變量。

  2. 不要讓子類能夠隨意訪問、修改父類的方法。父類中那些僅爲輔助其餘的工具方法,應該使用private訪問控制符修飾,讓子類沒法訪問該方法;若是父類中的方法須要被外部類調用,則必須以public修飾,但又不但願子類重寫該方法,可使用final修飾符(該修飾符後面會有更詳細的介紹)來修飾該方法。若是但願父類的某個方法被子類重寫,但不但願被其餘類自由訪問,則可使用protected來修飾該方法。

  3. 儘可能不要在父類構造器中調用將要被子類重寫的方法。 將引起空指針異常。

若是想把某些類設置成最終類,既不能被當成父類,則可使用final修飾這個類;使用private修飾這個類的全部構造器,從而保證子類沒法調用該類的構造器,也就沒法繼承該類的實例。

什麼時候須要從父類派生新的子類:

  1. 子類須要額外增長屬性,而不只僅是屬性值的改變。

  2. 子類須要增長本身獨有的行爲方式(包括增長新的方法或重寫父類的方法)。

利用組合實現複用

對於繼承而已,子類能夠直接得到父類的public方法,程序使用子類時,將能夠直接訪問該子類從父類那裏繼承到的方法;而組合則是把舊類對象做爲新類的成員變量組合進來,用以實現新類的功能,用戶看到的是新類的方法,而不能看到被組合對象的方法。

繼承要表達的是一種「是(is-a)」的關係,而組合表達的是「有(has-a)」的關係。

初始化塊

使用初始化塊

初始化塊是Java類裏可出現的第4種成員(前面依次有成員變量、方法和構造器),一個類裏能夠有多個初始化塊,相同類型的初始化塊之間有順序:前面定義的初始化塊先執行,後面定義的初始化塊後執行。

[修飾符]
{
    //初始化塊的可執行性代碼
}

初始化塊的修飾符只能是static,使用static修飾的初始化塊被稱爲靜態初始化塊。初始化塊裏的代碼能夠包含任何可執行性語句,包括定義局部變量、調用其餘對象的方法,以及使用分支、循環語句等。

當建立Java對象時,系統老是先調用該類裏定義的初始化塊,若是一個類裏定義了2個普通初始化塊,則前面定義的初始化塊先執行,後面定義的初始化塊後執行。初始化塊只在建立Java對象時隱式執行,並且在執行構造器以前執行。

初始化塊和構造器

與構造器不一樣的是,初始化塊是一段固定執行的代碼,它不能接收任何參數。

靜態初始化塊

若是定義初始化塊時使用了static修飾符,則這個初始化塊就變成了靜態初始化塊,也被稱爲類初始化塊(普通初始化塊負責對對象執行初始化,類初始化塊則負責對類進行初始化)。靜態初始化塊是類相關的,系統將在類初始化階段執行靜態初始化塊,而不是在建立對象時才執行。所以靜態初始化塊老是比普通初始化塊先執行。

靜態初始化塊也被稱爲類初始化塊,也屬於類的靜態成員,一樣須要遵循靜態成員不能訪問非靜態成員的規則,所以靜態初始化塊不能訪問非靜態成員,包括不能訪問實例變量和實例方法。

系統在類初始化階段執行靜態初始化塊時,不只會執行本類的靜態初始化塊,並且還會一直上溯到java.lang.Object類(若是它包含靜態初始化塊),先執行java.lang.Object類的靜態初始化塊(若是有),而後執行其父類的靜態初始化塊······最後才執行該類的靜態初始化塊。
系統在建立一個Java對象時,不只會執行該類的普通初始化塊和構造器,並且系統會一直上溯到java.lang.Object類,先執行java.lang.Object類的初始化塊,開始執行java.lang.Object的構造器,一次向下執行其父類的初始化塊,開始執行其父類的構造器······最後才執行該類的初始化塊和構造器,返回該類的對象。

當JVM第一次主動使用某個類時,系統會在類準備階段爲該類的全部靜態成員變量分配內存;在初始化階段則負責初始化這些靜態成員變量,初始化靜態成員變量就是執行類初始化代碼或者聲明類成員變量時指定的初始值,它們的執行順序與源代碼中的排列順序相同。

相關文章
相關標籤/搜索