接上文《JavaSE 基礎(chǔ)學(xué)習(xí)之二 —— Java 的部分基本語(yǔ)法》
三. Java 的繼承與接口
1. java 中的繼承
繼承是 java 面向?qū)ο缶幊碳夹g(shù)的一塊基石锋爪,因?yàn)樗试S創(chuàng)建分等級(jí)層次的類贾富。
繼承就是<font color=red>子類繼承父類的特征和行為,使得子類對(duì)象(實(shí)例)具有父類的實(shí)例域和方法</font>侥猩,或子類從父類繼承方法,使得子類具有父類相同的行為。
——摘自《Java 繼承 | 菜鳥(niǎo)教程》
繼承使用的關(guān)鍵字是 extend,格式為:
class 子類 extends 父類 {
}
用圓形為例姑蓝,舉例如下:
public class Circle extends Shape {
// ...
}
繼承是使用已存在的類的定義作為基礎(chǔ)建立新類的技術(shù),新類的定義可以增加新的數(shù)據(jù)或新的功能吕粗,也可以用父類的功能纺荧,但不能選擇性地繼承父類。通過(guò)使用繼承我們能夠非常方便地復(fù)用以前的代碼颅筋,能夠大大的提高開(kāi)發(fā)的效率宙暇。此外,繼承的代碼復(fù)用體現(xiàn)了一種 is-a 關(guān)系:比如PC 機(jī)是計(jì)算機(jī)垃沦,工作站也是計(jì)算機(jī)客给。PC 機(jī)和工作站是兩種不同類型的計(jì)算機(jī)用押,但都繼承了計(jì)算機(jī)的共同特性肢簿。因此在用 Java 語(yǔ)言實(shí)現(xiàn)時(shí),應(yīng)該將 PC 機(jī)和工作站定義成兩種類蜻拨,均繼承計(jì)算機(jī)類池充。
Java 中除了構(gòu)造函數(shù)之外,子類可以繼承父類所有函數(shù)缎讼。
關(guān)于子類的構(gòu)造函數(shù)收夸,其實(shí)子類是可以通過(guò) super() 方法訪問(wèn)到父類的構(gòu)造函數(shù)的。子類的無(wú)參構(gòu)造函數(shù)血崭,默認(rèn)調(diào)用父類無(wú)參數(shù)的構(gòu)造函數(shù)卧惜。如果要顯式的調(diào)用構(gòu)造函數(shù),需要使用 super 關(guān)鍵字夹纫,而且要把 super() 放在子類構(gòu)造函數(shù)的第一句咽瓷,就可以在子類中調(diào)用父類的構(gòu)造函數(shù)了。大致如下所示:
public Circle extends Shape {
public Shape() {
// 調(diào)用父類構(gòu)造函數(shù)
super();
//...其他初始化方法....
}
}
注:關(guān)于 super 關(guān)鍵字:
- super 關(guān)鍵字也有兩種意義:調(diào)用父類的方法舰讹,或是調(diào)用父類的構(gòu)造器茅姜。但是,super并不表示一個(gè)指向?qū)ο蟮囊迷孪唬皇且粋€(gè)特殊的關(guān)鍵字钻洒,用來(lái)告訴編譯器,現(xiàn)在要調(diào)用的是父類的方法锄开。
- 理論上素标,子類一定會(huì)調(diào)用父類相應(yīng)的構(gòu)造函數(shù),只是使用了 super 關(guān)鍵字是顯式的調(diào)用而已萍悴,而且通常情況下 super 關(guān)鍵字的調(diào)用時(shí)都被省略了糯钙;
2. 動(dòng)態(tài)綁定 (Dynamic Binding)
程序綁定指的是一個(gè)方法的調(diào)用與方法所在的類關(guān)聯(lián)起來(lái)粪狼。對(duì) Java 來(lái)說(shuō),綁定分為<font color=red>靜態(tài)綁定</font>和<font color=red>動(dòng)態(tài)綁定</font>(或者叫做前期綁定和后期綁定)任岸。
靜態(tài)綁定是指在程序執(zhí)行前方法已經(jīng)被綁定再榄,也就是說(shuō)在編譯過(guò)程中,就已經(jīng)知道該方法是屬于哪個(gè)類中的方法享潜。此時(shí)由編譯器或其它連接程序?qū)崿F(xiàn)困鸥。針對(duì) Java,可以簡(jiǎn)單理解為程序編譯期的綁定剑按。這里特別說(shuō)明一點(diǎn)疾就,Java 當(dāng)中的方法只有 <font color=red>final, static, private 和構(gòu)造方法</font>是靜態(tài)綁定。(具體分析見(jiàn)參考網(wǎng)址)
動(dòng)態(tài)綁定即后期綁定艺蝴,指在運(yùn)行時(shí)根據(jù)具體對(duì)象的類型進(jìn)行綁定猬腰。如果一種語(yǔ)言實(shí)現(xiàn)了后期綁定(如 Java, C++),同時(shí)必須提供一些機(jī)制猜敢,可在運(yùn)行期間判斷對(duì)象的類型姑荷,并分別調(diào)用適當(dāng)?shù)姆椒?/strong>。也就是說(shuō)缩擂,在運(yùn)行時(shí)編譯器依然不知道對(duì)象的類型鼠冕,但方法調(diào)用機(jī)制能自己去調(diào)查,找到正確的方法主體胯盯。不同的語(yǔ)言對(duì)后期綁定的實(shí)現(xiàn)方法是有所區(qū)別的懈费,但我們至少可以這樣認(rèn)為:它們都要在對(duì)象中安插某些特殊類型的信息。
動(dòng)態(tài)綁定的典型博脑,就是父類的引用可以引用任何子類的實(shí)例憎乙。比如有如下父類子類關(guān)系,介紹說(shuō)明動(dòng)態(tài)綁定的具體過(guò)程:
Parent p = new Children();
- 編譯器檢查對(duì)象的聲明類型和方法名叉趣;
- 假如我們有 Children 的實(shí)例對(duì)象 child泞边,此時(shí)想要調(diào)用 Children 的 fun(args) 方法,那么編譯器就會(huì)列舉出所有名稱為 fun 的方法(所有方法簽名相同君账,參數(shù)列表不同的 fun 方法)繁堡,并列舉 Children 的超類 Parent 中 fun 方法;
- 編譯器檢查方法調(diào)用中提供的參數(shù)類型乡数;
- 如果所有簽名為 fun 的方法中椭蹄,有一個(gè)參數(shù)類型和調(diào)用時(shí)提供的參數(shù)類型最匹配,那么就調(diào)用該方法净赴。該過(guò)程成為<font color=red>重載解析</font>绳矩;
- 當(dāng)程序運(yùn)行并使用動(dòng)態(tài)綁定調(diào)用方法時(shí),虛擬機(jī)必須調(diào)用與 child 指向的對(duì)象的實(shí)際類型相匹配的方法版本玖翅。調(diào)用方法的時(shí)候翼馆,如果當(dāng)前子類已經(jīng)對(duì)父類實(shí)現(xiàn)了方法的重寫(xiě)割以,則調(diào)用子類重寫(xiě)后的方法;否則只調(diào)用父類的方法应媚。即如果子類 Children 中如果實(shí)現(xiàn)了對(duì)應(yīng)的 fun(args) 方法严沥,則調(diào)用 Children 的方法,否則就在父類 Parent 中尋找中姜;在 Parent 中找不到消玄,則在 Parent 的父類中找,直到最頂層的父類丢胚。
JVM 調(diào)用一個(gè)類方法時(shí)(即標(biāo)注 static 的靜態(tài)方法)翩瓜,它會(huì)基于對(duì)象引用的類型來(lái)選擇所調(diào)用的方法,通常在編譯時(shí) JVM 就知道了要調(diào)用什么方法携龟,這就是靜態(tài)綁定兔跌。相反,如果 JVM 調(diào)用一個(gè)實(shí)例對(duì)象方法時(shí)峡蟋,它會(huì)基于對(duì)象實(shí)際的類型來(lái)選擇所調(diào)用的方法坟桅,具體調(diào)用什么方法只能在運(yùn)行時(shí)得知。這就是動(dòng)態(tài)綁定层亿,是多態(tài)的一種桦卒。動(dòng)態(tài)綁定為解決實(shí)際的業(yè)務(wù)問(wèn)題提供了很大的靈活性立美,是一種非常優(yōu)美的機(jī)制匿又。
參考網(wǎng)址:《Java靜態(tài)綁定與動(dòng)態(tài)綁定》
3. 類的初始化順序
創(chuàng)建一個(gè)實(shí)例對(duì)象時(shí),考慮到該對(duì)象的父子關(guān)系建蹄,JVM 按照一定的順序進(jìn)行初始化:
- 先父類靜態(tài)碌更,再子類靜態(tài)
- 父類的定義初始化 + 構(gòu)造函數(shù)
- 子類定義初始化 + 構(gòu)造函數(shù)
以例程來(lái)說(shuō)明初始化順序:
package oop4;
public class Test2 {
public static void main(String[] args) {
D d = new D();
}
}
class C{
// C 的定義初始化
{System.out.println("aa..");}
// C 的靜態(tài)初始化
static{
System.out.println("bb..");
}
// C 的構(gòu)造函數(shù)
C(){System.out.println("cc..");}
}
class D extends C{
// D 的定義初始化
{System.out.println("dd...");}
// D 的靜態(tài)初始化
static{
System.out.println("ee..");
}
// D 的構(gòu)造函數(shù)
D(){
System.out.println("ff...");
}
}
分析該段程序,先后順序應(yīng)該如下:
- 父類 C 的靜態(tài)初始化:
bb..
- 子類 D 的靜態(tài)初始化:
ee..
- 父類 C 的定義初始化:
aa..
- 父類 C 的構(gòu)造函數(shù):
cc..
- 子類 D 的定義初始化:
dd...
- 子類 D 的構(gòu)造函數(shù):
ff...
綜上所述洞慎,該段程序輸出的結(jié)果:
bb..
ee..
aa..
cc..
dd...
ff...
4. Java 的單繼承
Java 中的繼承只能是<font color=red>單一繼承</font>痛单,即 extends 關(guān)鍵字只能有一個(gè)類名;但 java 的繼承具有傳遞性劲腿。
為什么 Java 只能單繼承旭绒,而不像 C++ 一樣能夠多繼承?從技術(shù)的角度來(lái)說(shuō)焦人,是為了降低復(fù)雜性挥吵。例如,A 類中有一個(gè) m 方法花椭,B 類中也有一個(gè) m 方法忽匈。如果 C 類單獨(dú)繼承 A 類或者 B 類時(shí),C 類中的 m 方法要么繼承于 A 類矿辽,要么繼承于 B 類丹允。而如果多重繼承的話郭厌,C 類的 m 方法有可能來(lái)自 A 類,又有可能來(lái)自 B 類雕蔽,就會(huì)造成沖突折柠。這樣的繼承關(guān)系,就會(huì)增加復(fù)雜性批狐,甚至進(jìn)一步影響多態(tài)的靈活性液走。
此外,java.lang.Object 是一切類的父類贾陷≡悼簦或者可以說(shuō),如果一個(gè)類沒(méi)有父類髓废,那么它的父類就是 java.lang.Object巷懈。Object 類型有幾個(gè)方法比較實(shí)用:
-
equals 方法:用來(lái)判斷兩個(gè) obj 對(duì)象的地址是否相等。
- 由于 Object 的原始 equals 方法比較時(shí)慌洪,比較雙方如果地址相同顶燕,則返回 true,否則返回 false冈爹,所以對(duì)于很多 Object 的子類并不適用涌攻,故很多 Object 的子類經(jīng)常會(huì)重寫(xiě) equals 方法。以后如果有調(diào)用 equals 方法的時(shí)候频伤,需要了解該 equals 方法的具體意義恳谎;
- toString() 方法:打印一個(gè)對(duì)象,就會(huì)打印該對(duì)象的 toString 的返回值憋肖;
如果要判斷一個(gè)實(shí)例對(duì)象 obj 是否屬于某個(gè)類型 T因痛,可以使用關(guān)鍵字 instanceof。對(duì)于表達(dá)式 obj instanceof T
岸更,如果實(shí)例 obj 屬于 T 類型鸵膏,則返回 true;否則返回 false怎炊。
5. 抽象類
對(duì)于普通的類谭企,其本身就是一個(gè)完善的功能類,可以直接產(chǎn)生實(shí)例化對(duì)象评肆,并且在普通類中可以包含構(gòu)造方法债查、普通方法、static 方法糟港、常量和變量等內(nèi)容攀操。抽象類,就是指在普通類的結(jié)構(gòu)里面增加抽象方法的組成部分秸抚。
那么什么叫抽象方法呢速和?抽象方法歹垫,是指沒(méi)有方法體的方法,即一個(gè)方法只有聲明颠放,沒(méi)有實(shí)現(xiàn)排惨。同時(shí)抽象方法還必須用 abstract 關(guān)鍵字來(lái)聲明。只要擁有一個(gè)抽象方法的類就是抽象類碰凶。
抽象類的使用原則如下:
- 抽象方法必須為 public 或者 protected(因?yàn)槿绻麨?private暮芭,則不能被子類繼承,子類便無(wú)法實(shí)現(xiàn)該方法)欲低,缺省情況下辕宏,默認(rèn)為public;
- 抽象類不能直接實(shí)例化砾莱,需要依靠子類采用向上轉(zhuǎn)型的方式處理瑞筐;
- 抽象類必須有子類,使用 extends 繼承腊瑟,一個(gè)子類只能繼承一個(gè)抽象類聚假;
- 對(duì)于不是抽象類的子類,必須覆寫(xiě)抽象類之中的全部抽象方法(如果子類沒(méi)有實(shí)現(xiàn)父類的抽象方法闰非,則必須將子類也定義為 abstract 類)膘格;
對(duì)于抽象類,還有一些需要注意的地方:
- 抽象類繼承子類财松,其中有明確的方法覆寫(xiě)要求瘪贱,而普通類可以有選擇性的來(lái)決定是否需要覆寫(xiě);
- 抽象類實(shí)際上就比普通類多了一些抽象方法而已游岳,其他組成部分和普通類完全一樣政敢;
- 普通類對(duì)象可以直接實(shí)例化其徙,但抽象類的對(duì)象必須經(jīng)過(guò)向上轉(zhuǎn)型之后才可以得到胚迫。
可以看出,雖然一個(gè)類的子類可以去繼承任意的一個(gè)普通類唾那,可是從開(kāi)發(fā)的實(shí)際要求來(lái)講访锻,普通類盡量不要去繼承另外一個(gè)普通類,而是去繼承抽象類闹获。
6. final 關(guān)鍵字
在 Java 中期犬,final 關(guān)鍵字可以用來(lái)修飾類、方法和變量(包括成員變量和局部變量)避诽。
用 final 關(guān)鍵字修飾變量:
- final 關(guān)鍵字來(lái)修飾類的變量龟虎,只能被賦一次值。
- final 修飾的成員變量也只能賦值一次沙庐;但在對(duì)象創(chuàng)建的時(shí)候鲤妥,成員變量必須賦值佳吞,即在定義初始化或構(gòu)造函數(shù)中對(duì) final 修飾的成員變量進(jìn)行賦值;
- java 語(yǔ)言中沒(méi)有常量棉安,但可以<font color=red>通過(guò) public static final</font> 來(lái)定義常量底扳,且一般大寫(xiě);
- 例:
public static final int CELL_WIDTH = 50
;
- 例:
用 final 關(guān)鍵字修飾的類不能被繼承贡耽;例如衷模,String, Math 類就是 Java 中典型的 final 關(guān)鍵字修飾的類;
用 final 關(guān)鍵字修飾的方法蒲赂,不能夠被重寫(xiě)阱冶。
需要注意的是切心,用 final 修飾的數(shù)組宝当,與普通的變量理解起來(lái)難度。如下例中:
//========================================
final int a = 10;
a = 20; // 錯(cuò)誤盖灸,a 變量只能賦值一次
//========================================
final int[] b = {1, 2, 3, 4};
b[0] = 10; // 正確
//========================================
int 類型的 a 由于被 final 關(guān)鍵字修飾氏涩,所以不能被二次賦值届囚,這比較容易理解。但下面的例子中是尖,看起來(lái)好像是數(shù)組的二次賦值也可以完成意系。其實(shí)實(shí)際上對(duì)于被 final 關(guān)鍵字修飾的數(shù)組而言,數(shù)組的引用地址是不能改變的饺汹。上例程中蛔添,b[0] = 10 僅改變了 b 數(shù)組 0 位置的元素內(nèi)容而已,而該位置的地址引用沒(méi)有發(fā)生任何改變兜辞,所以是可以完成的迎瞧。
7. 接口
接口體現(xiàn)的是一種標(biāo)準(zhǔn),外部體現(xiàn)為方法的聲明逸吵。接口用關(guān)鍵字 interface 修飾凶硅。提供一個(gè)接口,是為了實(shí)現(xiàn)某種標(biāo)準(zhǔn)的對(duì)接過(guò)程扫皱,而實(shí)現(xiàn)接口足绅,就是意味著符合這個(gè)標(biāo)準(zhǔn)。對(duì)接口的實(shí)現(xiàn)韩脑,需要使用 implements 關(guān)鍵字氢妈。實(shí)現(xiàn)一個(gè)接口,就要重寫(xiě)接口中的方法段多;換個(gè)角度來(lái)說(shuō)首量,如果不實(shí)現(xiàn)接口,就變成了一個(gè)抽象類。
接口里的方法加缘,默認(rèn)都是 public abstract 類型的粥航。此外接口里也可以聲明變量,變量的類型也默認(rèn)為 public static final 類型生百。例如:
public interface Memory {
public void memo(); // 等價(jià)于 public abstract void memo();
int i = 1; // 等價(jià)于 public static final int i = 1;
}
Java 中的接口與繼承最大的不同是递雀,繼承是單一繼承,但接口與接口之間可以多繼承蚀浆。此外一個(gè)類可以繼承一個(gè)父類缀程,同時(shí)實(shí)現(xiàn)多個(gè)接口。舉一個(gè)例子市俊,如何定義一個(gè)英雄杨凑?我們假定一個(gè)人,如果同時(shí)滿足可以飛摆昧、可以打架撩满、可以游泳,那么他就是一個(gè)英雄绅你。同時(shí)伺帘,人又屬于動(dòng)物。那么我們就可以定義英雄 Hero 如下:
public class Hero extends Animal implemets CanFly, CanFight, CanSwim {}
上例中忌锯,也可以看到接口與繼承的另一個(gè)區(qū)別:繼承體現(xiàn)了 is-a 關(guān)系(單繼承)伪嫁,接口體現(xiàn)了 can-do 關(guān)系(多繼承)。
接口與抽象又有一些相似的共同點(diǎn):如果看到接口類型的引用偶垮,那么引用的一定是實(shí)現(xiàn)了該接口的類的實(shí)例张咳;如果看到抽象類型的引用,那么引用的一定是繼承了該抽象類的類的實(shí)例似舵。
8. 內(nèi)部類
使用內(nèi)部類的原因脚猾,在于內(nèi)部類提供了更好的封裝,只有外部類可以訪問(wèn)內(nèi)部類砚哗。此外內(nèi)部類中的屬性和方法龙助,即使是外部類也不能直接訪問(wèn),相反频祝,內(nèi)部類可以直接訪問(wèn)包括 private 聲明的外部類的屬性和方法泌参。另外屬于內(nèi)部類的匿名內(nèi)部類也十分利于回調(diào)函數(shù)的編寫(xiě)。
內(nèi)部類與外部類是一個(gè)相對(duì)獨(dú)立的實(shí)體常空,它與外部類并不是 is-a 關(guān)系。比如我們定義了內(nèi)部類外部類的 OuterClass.java 如下:
public class OuterClass {
private String outerName;
private int outerAge;
public class InnerClass{
private String innerName;
private int innerAge;
}
}
在該文件的路徑下輸入指令:
javac OuterClass.java
結(jié)果如圖:
從編譯的結(jié)果就可以看出來(lái)盖溺,編譯后外部類及其內(nèi)部類會(huì)生成兩個(gè)獨(dú)立的 .class 文件:OuterClass.class 和 OuterClass$InnerClass.class漓糙。說(shuō)明內(nèi)部類是一個(gè)編譯時(shí)的概念。
此外烘嘱,內(nèi)部類可以直接訪問(wèn)外部類的元素昆禽,但是外部類不可以直接訪問(wèn)內(nèi)部類的元素蝗蛙;而且外部類可以通過(guò)內(nèi)部類引用間接訪問(wèn)內(nèi)部類元素。
關(guān)于內(nèi)部類的創(chuàng)建醉鳖,如果在外部類中創(chuàng)建內(nèi)部類捡硅,那么就和普通的創(chuàng)建對(duì)象是一樣的:
InnerClass innerClass = new InnerClass();
如果在外部類之外創(chuàng)建外部類中的內(nèi)部類(有點(diǎn)拗口),就需要 outerClass.new 來(lái)創(chuàng)建:
//================================================
OuterClass outerClass = new OuterClass();
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
//================================================
// 或者一步到位的方法:
OuterClass.InnerClass innerClass = new OuterClass().new InnerClass();
//================================================
Java中內(nèi)部類主要分為四種:成員內(nèi)部類盗棵、方法內(nèi)部類壮韭、匿名內(nèi)部類、靜態(tài)內(nèi)部類纹因。
(1) 成員內(nèi)部類
成員內(nèi)部類也是最普通的內(nèi)部類喷屋,上面的 InnerClass 與 OuterClass就是屬于成員內(nèi)部類與其外部類。成員內(nèi)部類又稱為局部?jī)?nèi)部類瞭恰,它是外部類的一個(gè)成員屯曹,所以他是可以無(wú)限制的訪問(wèn)外圍類的所有成員屬性和方法,盡管是 private 的惊畏,但是外部類要訪問(wèn)內(nèi)部類的成員屬性和方法恶耽,就需要通過(guò)內(nèi)部類實(shí)例來(lái)訪問(wèn)。
在成員內(nèi)部類中要注意兩點(diǎn):
- 成員內(nèi)部類中不能存在任何 static 的變量和方法
- 成員內(nèi)部類是依附于外圍類的颜启,所以只有先創(chuàng)建了外圍類才能夠創(chuàng)建內(nèi)部類
(2) 靜態(tài)內(nèi)部類
static 關(guān)鍵字可以修飾成員變量驳棱、方法、代碼塊农曲,其實(shí)它還可以修飾內(nèi)部類社搅,使用 static 修飾的內(nèi)部類我們稱之為靜態(tài)內(nèi)部類。靜態(tài)內(nèi)部類與非靜態(tài)內(nèi)部類之間存在一個(gè)最大的區(qū)別乳规,我們知道非靜態(tài)內(nèi)部類在編譯完成之后會(huì)隱含地保存著一個(gè)引用形葬,該引用是指向創(chuàng)建它的外圍內(nèi),但是靜態(tài)內(nèi)部類卻沒(méi)有暮的。沒(méi)有這個(gè)引用就意味著靜態(tài)內(nèi)部類的兩個(gè)屬性:
- 靜態(tài)內(nèi)部類的創(chuàng)建不需要依賴于外圍類笙以,可以直接創(chuàng)建;
- 靜態(tài)內(nèi)部類不可以使用任何外圍類的非 static 成員變量和方法冻辩,而內(nèi)部類則都可以猖腕;
靜態(tài)內(nèi)部類的示例如下:
public class OuterClass {
private static String outerName;
public int age;
static class InnerClass1{
// 在靜態(tài)內(nèi)部類中可以存在靜態(tài)成員
public static String _innerName = "static variable";
public void display(){
/*=========================================
* 靜態(tài)內(nèi)部類只能訪問(wèn)外部類的靜態(tài)成員變量和方法
* 不能訪問(wèn)外部類的非靜態(tài)成員變量和方法
==========================================
*/
System.out.println("OutClass name :" + outerName);
}
}
class InnerClass2{
// 非靜態(tài)內(nèi)部類中不能存在靜態(tài)成員
public String _innerName = "no static variable";
// 非靜態(tài)內(nèi)部類中可以調(diào)用外部類的任何成員,不管是靜態(tài)的還是非靜態(tài)的
public void display() {
System.out.println("OuterClass name:" + outerName);
System.out.println("OuterClass age:" + age);
}
}
public void display(){
// 外部類能直接訪問(wèn)靜態(tài)內(nèi)部類靜態(tài)元素
System.out.println(InnerClass1._innerName);
// 靜態(tài)內(nèi)部類可以直接創(chuàng)建實(shí)例不需要依賴于外部類
new InnerClass1().display();
// 非靜態(tài)內(nèi)部的創(chuàng)建需要依賴于外部類
OuterClass.InnerClass2 inner2 = new OuterClass().new InnerClass2();
// 非靜態(tài)內(nèi)部類的成員需要使用非靜態(tài)內(nèi)部類的實(shí)例訪問(wèn)
System.out.println(inner2._innerName);
inner2.display();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.display();
}
}
(3) 方法內(nèi)部類
方法內(nèi)部類定義在外部類的方法中,局部?jī)?nèi)部類和成員內(nèi)部類基本一致恨闪,只是它們的作用域不同倘感,方法內(nèi)部類只能在該方法中被使用,出了該方法就會(huì)失效咙咽。 對(duì)于這個(gè)類的使用主要是應(yīng)用與解決比較復(fù)雜的問(wèn)題老玛,想創(chuàng)建一個(gè)類來(lái)輔助我們的解決方案,到那時(shí)又不希望這個(gè)類是公共可用的,所以就產(chǎn)生了局部?jī)?nèi)部類蜡豹。
(4) 匿名內(nèi)部類
匿名內(nèi)部類是沒(méi)有名字的局部?jī)?nèi)部類麸粮,它沒(méi)有 class, interface, implements, extends 等關(guān)鍵字的修飾,也沒(méi)有構(gòu)造器镜廉,它一般隱式的繼承某一個(gè)父類弄诲,或者具體實(shí)現(xiàn)某一個(gè)接口。
-
什么時(shí)候用:
- 已知父類娇唯,要獲取其子類的實(shí)例對(duì)象齐遵;
- 已知接口,要獲取其實(shí)現(xiàn)了該接口的類的實(shí)例视乐;
- 怎么用:
對(duì)于子類繼承:
new 父類(給父類的構(gòu)造函數(shù)傳遞參數(shù)) {
// 子類具體實(shí)現(xiàn)部分洛搀;
}
// 此處得到的是子類的實(shí)例對(duì)象
對(duì)于接口實(shí)現(xiàn):
new 接口() {
// 實(shí)現(xiàn)了該接口的類的實(shí)現(xiàn)部分;
}
// 此處得到的是接口的實(shí)現(xiàn)類的實(shí)例對(duì)象
后面將會(huì)在很多地方看到匿名內(nèi)部類的使用佑淀,比如在后面講到的 TreeSet留美,JDBC 的 JdbcTemplate.query 方法中的 RowMapper 繼承類實(shí)現(xiàn)等。此處以 TreeSet 為例伸刃,需要實(shí)現(xiàn)一個(gè)比較器 Comparator 的 compareTo 方法谎砾,這里就可以實(shí)現(xiàn)匿名內(nèi)部類。代碼如下:
TreeSet<T> ts = new TreeSet<T>(new Comparator<T>() {
public int compare(T o1, T o2) {
// TODO Auto-generated method stub
return o2.getName().compareTo(o1.getName());
}
});
上面的代碼中捧颅,new TreeSet< T > 后面?zhèn)魅氲膮?shù)景图,是直接定義得到的一個(gè) new Comparator< T >(){...} 。這里就體現(xiàn)了匿名內(nèi)部類直接對(duì)接口的實(shí)現(xiàn)碉哑,確定了數(shù)據(jù)類型為 T 的兩個(gè)對(duì)象 o1, o2 的名稱按照字母順序進(jìn)行排列的規(guī)定挚币。
后續(xù)的 RowMapper 繼承,也會(huì)用到匿名內(nèi)部類扣典。代碼大致如下妆毕,到后面會(huì)詳細(xì)講解:
@Test
public void testResultSet1() {
jdbcTemplate.update("insert into test(name) values('name5')");
String listSql = "select * from test";
List result = jdbcTemplate.query(listSql, new RowMapper<Map>() {
@Override
public Map mapRow(ResultSet rs, int rowNum) throws SQLException {
Map row = new HashMap();
row.put(rs.getInt("id"), rs.getString("name"));
return row;
}});
Assert.assertEquals(1, result.size());
jdbcTemplate.update("delete from test where name='name5'");
}
內(nèi)部類相關(guān)內(nèi)容參考地址:《
java 內(nèi)部類(inner class)詳解》