Java面向?qū)ο笕N特性-繼承,封裝,多態(tài)
1.繼承
1.繼承的概念
????1.類的公共屬性【字段,方法】
????2.把公共的屬性提取出來形成一個公共的類,讓其他的類去繼承:減少代碼的冗余度
? ??泛化:在多個子類的基礎(chǔ)上面抽取共有屬性的行為到一個父類中去
? ??特化:在一個父類的基礎(chǔ)上拓展子類特有的屬性和行為,生成一個新的子類
????原則:父類存放共性,子類存放特性
2.繼承的語法及可繼承的東西
1. 繼承到父類的那些東西
????(1) 非私有的字段及方法
????(2) 父類特性
????構(gòu)造方法不能夠被繼承:java規(guī)定
????子類可以繼承到父類的特性
3.繼承的特點(diǎn)
????(1) 單繼承
????(2) 多重繼承
????(3) 沒有顯示的繼承,那么隱式的繼承Object
4.方法的覆寫
子類如果對繼承的父類的方法不滿意(不適合),可以自己編寫繼承的方法,這種方式就稱為方法的覆寫。當(dāng)調(diào)用方法時會優(yōu)先調(diào)用子類的方法。
覆寫要注意:
? ?a、返回值類型
b、方法名
c、參數(shù)類型及個數(shù)
都要與父類繼承的方法相同,才叫方法的覆寫。
重載和覆寫的區(qū)別:
方法重載:在同一個類中處理不同數(shù)據(jù)的多個相同方法名的多態(tài)手段。
方法覆寫:相對繼承而言,子類中對父類已經(jīng)存在的方法進(jìn)行區(qū)別化的修改。
? ??繼承的初始化順序
1、初始化父類再初始化子類
2、先執(zhí)行初始化對象中屬性,再執(zhí)行構(gòu)造方法中的初始化。
基于上面兩點(diǎn),我們就知道實(shí)例化一個子類,java程序的執(zhí)行順序是:
父類對象屬性初始化---->父類對象構(gòu)造方法---->子類對象屬性初始化--->子類對象構(gòu)造方法
5.final關(guān)鍵字
使用final關(guān)鍵字做標(biāo)識有“最終的”含義。
1. final 修飾類,則該類不允許被繼承。
2. final 修飾方法,則該方法不允許被覆蓋(覆寫)。
3. final 修飾屬性,則該類的該屬性不會進(jìn)行隱式的初始化,所以 該final 屬性的初始化屬性必須有值,或在構(gòu)造方法中賦值(但只能選其一,且必須選其一,因?yàn)闆]有默認(rèn)值!),且初始化之后就不能改了,只能賦值一次。
4. final 修飾變量,則該變量的值只能賦一次值,在聲明變量的時候才能賦值,即變?yōu)?b>常量。
6.super關(guān)鍵字
在對象的內(nèi)部使用,可以代表父類對象。
1、訪問父類的屬性:super.age
2、訪問父類的方法:super.eat()
super的應(yīng)用:
首先我們知道子類的構(gòu)造的過程當(dāng)中必須調(diào)用父類的構(gòu)造方法。其實(shí)這個過程已經(jīng)隱式地使用了我們的super關(guān)鍵字。
這是因?yàn)槿绻宇惖臉?gòu)造方法中沒有顯示調(diào)用父類的構(gòu)造方法,則系統(tǒng)默認(rèn)調(diào)用父類無參的構(gòu)造方法。
那么如果自己用super關(guān)鍵字在子類里調(diào)用父類的構(gòu)造方法,則必須在子類的構(gòu)造方法中的第一行。
要注意的是:如果子類構(gòu)造方法中既沒有顯示調(diào)用父類的構(gòu)造方法,而父類沒有無參的構(gòu)造方法,則編譯出錯。
(補(bǔ)充說明,雖然沒有顯示聲明父類的無參的構(gòu)造方法,系統(tǒng)會自動默認(rèn)生成一個無參構(gòu)造方法,但是,如果你聲明了一個有參的構(gòu)造方法,而沒有聲明無參的構(gòu)造方法,這時系統(tǒng)不會動默認(rèn)生成一個無參構(gòu)造方法,此時稱為父類有沒有無參的構(gòu)造方法。)
7.Object類
Object類是所有類的父類,如果一個類沒有使用extends關(guān)鍵字明確標(biāo)識繼承另一個類,那么這個類默認(rèn)繼承Object類。
Object類中的方法,適合所有子類?。?!
那么Object類中有什么主要的方法呢?
1、toString()
a. 在Object類里面定義toString()方法的時候返回的對象的哈希code碼(對象地址字符串)。
我們可以發(fā)現(xiàn),如果我們直接用System.out.print(對象)輸出一個對象,則運(yùn)行結(jié)果輸出的是對象的對象地址字符串,也稱為哈希code碼。如:
哈希碼是通過哈希算法生成的一個字符串,它是用來唯一區(qū)分我們對象的地址碼,就像我們的身份證一樣。
b. 可以通過重寫toString()方法表示出對象的屬性。
? 如果我們希望輸出一個對象的時候,不是它的哈希碼,而是它的各個屬性值,那我們可以通過重寫toString()方法表示出對象的屬性。
? 2、equals()
a、equals()----返回值是布爾類型。
b、默認(rèn)的情況下,比較的是對象的引用是否指向同一塊內(nèi)存地址-------對象實(shí)例化時,即給對象分配內(nèi)存空間,該內(nèi)存空間的地址就是內(nèi)存地址。使用方法如:dog.equals(dog2);
c、 如果是兩個對象,但想判斷兩個對象的屬性是否相同,則重寫equals()方法。
2.封裝
1、概念:
將類的某些信息隱藏在類內(nèi)部,不允許外部程序直接訪問,而是通過該類提供的方法來實(shí)現(xiàn)對隱藏信息的操作和訪問。
2、好處:
只能通過規(guī)定的方法訪問數(shù)據(jù)。
??? 隱藏類的實(shí)例細(xì)節(jié),方便修改和實(shí)現(xiàn)。
3、封裝的實(shí)現(xiàn)步驟

需要注意:對封裝的屬性不一定要通過get/set方法,其他方法也可以對封裝的屬性進(jìn)行操作。當(dāng)然最好使用get/set方法,比較標(biāo)準(zhǔn)。

4.this關(guān)鍵字
1.this關(guān)鍵字代表當(dāng)前對象
this.屬性 操作當(dāng)前對象的屬性
this.方法 調(diào)用當(dāng)前對象的方法。
2.封裝對象的屬性的時候,經(jīng)常會使用this關(guān)鍵字。
3.當(dāng)getter和setter函數(shù)參數(shù)名和成員函數(shù)名重合的時候,可以使用this區(qū)別。如:

5.Java 中的內(nèi)部類
內(nèi)部類( Inner Class )就是定義在另外一個類里面的類。與之對應(yīng),包含內(nèi)部類的類被稱為外部類。
那么問題來了:那為什么要將一個類定義在另一個類里面呢?清清爽爽的獨(dú)立的一個類多好啊??!
答:內(nèi)部類的主要作用如下:
1. 內(nèi)部類提供了更好的封裝,可以把內(nèi)部類隱藏在外部類之內(nèi),不允許同一個包中的其他類訪問該類。
2. 內(nèi)部類的方法可以直接訪問外部類的所有數(shù)據(jù),包括私有的數(shù)據(jù)。
3. 內(nèi)部類所實(shí)現(xiàn)的功能使用外部類同樣可以實(shí)現(xiàn),只是有時使用內(nèi)部類更方便。
內(nèi)部類可分為以下幾種:
????????成員內(nèi)部類
????????靜態(tài)內(nèi)部類
????????方法內(nèi)部類
????????匿名內(nèi)部類
3.多態(tài)
面向?qū)ο蟮淖詈笠粋€特性就是多態(tài),那么什么是多態(tài)呢?多態(tài)就是對象的多種形態(tài)。
java里的多態(tài)主要表現(xiàn)在兩個方面:
1.引用多態(tài)
父類的引用可以指向本類的對象;
父類的引用可以指向子類的對象;
這兩句話是什么意思呢,讓我們用代碼來體驗(yàn)一下,首先我們創(chuàng)建一個父類Animal和一個子類Dog,在主函數(shù)里如下所示:

注意:我們不能使用一個子類的引用來指向父類的對象,如:
。

這里我們必須深刻理解引用多態(tài)的意義,才能更好記憶這種多態(tài)的特性。為什么子類的引用不能用來指向父類的對象呢?我在這里通俗給大家講解一下:就以上面的例子來說,我們能說“狗是一種動物”,但是不能說“動物是一種狗”,狗和動物是父類和子類的繼承關(guān)系,它們的從屬是不能顛倒的。當(dāng)父類的引用指向子類的對象時,該對象將只是看成一種特殊的父類(里面有重寫的方法和屬性),反之,一個子類的引用來指向父類的對象是不可行的??!
2.方法多態(tài)
根據(jù)上述創(chuàng)建的兩個對象:本類對象和子類對象,同樣都是父類的引用,當(dāng)我們指向不同的對象時,它們調(diào)用的方法也是多態(tài)的。
創(chuàng)建本類對象時,調(diào)用的方法為本類方法;
創(chuàng)建子類對象時,調(diào)用的方法為子類重寫的方法或者繼承的方法;
使用多態(tài)的時候要注意:如果我們在子類中編寫一個獨(dú)有的方法(沒有繼承父類的方法),此時就不能通過父類的引用創(chuàng)建的子類對象來調(diào)用該方法!?。?/b>
注意: 繼承是多態(tài)的基礎(chǔ)。
3.引用類型轉(zhuǎn)換
了解了多態(tài)的含義后,我們在日常使用多態(tài)的特性時經(jīng)常需要進(jìn)行引用類型轉(zhuǎn)換。
引用類型轉(zhuǎn)換:
1.?向上類型轉(zhuǎn)換(隱式/自動類型轉(zhuǎn)換),是小類型轉(zhuǎn)換到大類型。
就以上述的父類Animal和一個子類Dog來說明,當(dāng)父類的引用可以指向子類的對象時,就是向上類型轉(zhuǎn)換。如:

2.?向下類型轉(zhuǎn)換(強(qiáng)制類型轉(zhuǎn)換),是大類型轉(zhuǎn)換到小類型(有風(fēng)險,可能出現(xiàn)數(shù)據(jù)溢出)。
將上述代碼再加上一行,我們再次將父類轉(zhuǎn)換為子類引用,那么會出現(xiàn)錯誤,編譯器不允許我們直接這么做,雖然我們知道這個父類引用指向的就是子類對象,但是編譯器認(rèn)為這種轉(zhuǎn)換是存在風(fēng)險的。如:

那么我們該怎么解決這個問題呢,我們可以在animal前加上(Dog)來強(qiáng)制類型轉(zhuǎn)換。如:

但是如果父類引用沒有指向該子類的對象,則不能向下類型轉(zhuǎn)換,雖然編譯器不會報錯,但是運(yùn)行的時候程序會出錯,如:

其實(shí)這就是上面所說的子類的引用指向父類的對象,而強(qiáng)制轉(zhuǎn)換類型也不能轉(zhuǎn)換?。?/p>
還有一種情況是父類的引用指向其他子類的對象,則不能通過強(qiáng)制轉(zhuǎn)為該子類的對象。如:

這是因?yàn)槲覀冊诰幾g的時候進(jìn)行了強(qiáng)制類型轉(zhuǎn)換,編譯時的類型是我們強(qiáng)制轉(zhuǎn)換的類型,所以編譯器不會報錯,而當(dāng)我們運(yùn)行的時候,程序給animal開辟的是Dog類型的內(nèi)存空間,這與Cat類型內(nèi)存空間不匹配,所以無法正常轉(zhuǎn)換。這兩種情況出錯的本質(zhì)是一樣的,所以我們在使用強(qiáng)制類型轉(zhuǎn)換的時候要特別注意這兩種錯誤!!下面有個更安全的方式來實(shí)現(xiàn)向下類型轉(zhuǎn)換。。。。
?3. instanceof運(yùn)算符,來解決引用對象的類型,避免類型轉(zhuǎn)換的安全性問題。
instanceof是Java的一個二元操作符,和==,>,<是同一類東東。由于它是由字母組成的,所以也是Java的保留關(guān)鍵字。它的作用是測試它左邊的對象是否是它右邊的類的實(shí)例,返回boolean類型的數(shù)據(jù)。
我們來使用instanceof運(yùn)算符來規(guī)避上面的錯誤,代碼修改如下:

利用if語句和instanceof運(yùn)算符來判斷兩個對象的類型是否一致。
補(bǔ)充說明:在比較一個對象是否和另一個對象屬于同一個類實(shí)例的時候,我們通??梢圆捎胕nstanceof和getClass兩種方法通過兩者是否相等來判斷,但是兩者在判斷上面是有差別的。Instanceof進(jìn)行類型檢查規(guī)則是:你屬于該類嗎?或者你屬于該類的派生類嗎?而通過getClass獲得類型信息采用==來進(jìn)行檢查是否相等的操作是嚴(yán)格的判斷,不會存在繼承方面的考慮;
總結(jié):在寫程序的時候,如果要進(jìn)行類型轉(zhuǎn)換,我們最好使用instanceof運(yùn)算符來判斷它左邊的對象是否是它右邊的類的實(shí)例,再進(jìn)行強(qiáng)制轉(zhuǎn)換。
4.抽象類
定義:抽象類前使用abstract關(guān)鍵字修飾,則該類為抽象類。
使用抽象類要注意以下幾點(diǎn):
1. 抽象類是約束子類必須有什么方法,而并不關(guān)注子類如何實(shí)現(xiàn)這些方法。
2. 抽象類應(yīng)用場景:
a. 在某些情況下,某個父類只是知道其子類應(yīng)該包含怎樣的方法,但無法準(zhǔn)確知道這些子類如何實(shí)現(xiàn)這些方法(可實(shí)現(xiàn)動態(tài)多態(tài))。
b. 從多個具有相同特征的類中抽象出一個抽象類,以這個抽象類作為子類的模板,從而避免子類設(shè)計(jì)的隨意性。
3. 抽象類定義抽象方法,只有聲明,不需要實(shí)現(xiàn)。抽象方法沒有方法體以分號結(jié)束,抽象方法必須用abstract關(guān)鍵字來修飾。如:

4、包含抽象方法的類是抽象類。抽象類中可以包含普通的方法,也可以沒有抽象方法。如:

5、抽象類不能直接創(chuàng)建,可以定義引用變量來指向子類對象,來實(shí)現(xiàn)抽象方法。
5.接口
1、概念
接口可以理解為一種特殊的類,由全局常量和公共的抽象方法所組成。也可理解為一個特殊的抽象類,因?yàn)樗谐橄蠓椒ā?/p>
如果說類是一種具體實(shí)現(xiàn)體,而接口定義了某一批類所需要遵守的規(guī)范,接口不關(guān)心這些類的內(nèi)部數(shù)據(jù),也不關(guān)心這些類里方法的實(shí)現(xiàn)細(xì)節(jié),它只規(guī)定這些類里必須提供的某些方法。(這里與抽象類相似)
2.接口定義的基本語法
[修飾符] [abstract]?interface?接口名 [extends父接口1,2....](多繼承){
0…n常量 (public static final)
0…n 抽象方法(public abstract)
}???????????????????????????????????????????????????? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
其中[ ]里的內(nèi)容表示可選項(xiàng),可以寫也可以不寫;接口中的屬性都是常量,即使定義時不添加public static final?修飾符,系統(tǒng)也會自動加上;接口中的方法都是抽象方法,即使定義時不添加public abstract修飾符,系統(tǒng)也會自動加上。
3.使用接口
一個類可以實(shí)現(xiàn)一個或多個接口,實(shí)現(xiàn)接口使用implements關(guān)鍵字。java中一個類只能繼承一個父類,是不夠靈活的,通過實(shí)現(xiàn)多個接口可以補(bǔ)充。
繼承父類實(shí)現(xiàn)接口的語法為:
[修飾符] class 類名?extends?父類?implements?接口1,接口2...{
類體部分//如果繼承了抽象類,需要實(shí)現(xiàn)繼承的抽象方法;要實(shí)現(xiàn)接口中的抽象方法
}
注意:如果要繼承父類,繼承父類必須在實(shí)現(xiàn)接口之前,即extends關(guān)鍵字必須在implements關(guān)鍵字前
補(bǔ)充說明:通常我們在命名一個接口時,經(jīng)常以I開頭,用來區(qū)分普通的類。如:IPlayGame