https://m.toutiao.com/is/e2Pfsdv/ 繼承 在前面的課程中,您多次看到繼承。在Java語(yǔ)言中,類可以從其他類派生,從而從這些類繼承字段和方法。 定義: 從另一個(gè)類派生的類稱為子類(也稱為派生類,擴(kuò)展類或子類)。派生子類的類稱為超類(也稱為基類或父類)。 繼承的概念很簡(jiǎn)單但是很強(qiáng)大:當(dāng)您要?jiǎng)?chuàng)建一個(gè)新類并且已經(jīng)有一個(gè)包含所需代碼的類時(shí),可以從現(xiàn)有類中派生新類。這樣,您可以重用現(xiàn)有類的字段和方法,而不必自己編寫(xiě)(和調(diào)試?。┧鼈儭?/span> 子類從其超類繼承所有成員(字段,方法和嵌套類)。構(gòu)造函數(shù)不是成員,因此它們不會(huì)被子類繼承,但是可以從子類中調(diào)用超類的構(gòu)造函數(shù)。 Java平臺(tái)類層次結(jié)構(gòu) 包中定義的 Object類java.lang定義并實(shí)現(xiàn)了所有類(包括您編寫(xiě)的類)共有的行為。在Java平臺(tái)中,許多類直接從派生Object,其他類從其中一些類派生,依此類推,形成了類的層次結(jié)構(gòu)。 Java平臺(tái)中的所有類都是對(duì)象的后代 在層次結(jié)構(gòu)的頂部,Object是所有類中最通用的。層次結(jié)構(gòu)底部附近的類提供了更特殊的行為。 繼承的例子 以下是“Bicycle類和對(duì)象”課程中介紹的類的可能實(shí)現(xiàn)的示例代碼: public class Bicycle { // the Bicycle class has three fields public int cadence; public int gear; public int speed; // the Bicycle class has one constructor public Bicycle(int startCadence, int startSpeed, int startGear) { gear = startGear; cadence = startCadence; speed = startSpeed; } // the Bicycle class has four methods public void setCadence(int newValue) { cadence = newValue; } public void setGear(int newValue) { gear = newValue; } public void applyBrake(int decrement) { speed -= decrement; } public void speedUp(int increment) { speed += increment; } } 類聲明的MountainBike類,它是一個(gè)子類Bicycle可能是這樣的: public class MountainBike extends Bicycle { // the MountainBike subclass adds one field public int seatHeight; // the MountainBike subclass has one constructor public MountainBike(int startHeight, int startCadence, int startSpeed, int startGear) { super(startCadence, startSpeed, startGear); seatHeight = startHeight; } // the MountainBike subclass adds one method public void setHeight(int newValue) { seatHeight = newValue; } } MountainBike繼承的所有字段和方法,Bicycle并添加字段seatHeight和設(shè)置它的方法。除了構(gòu)造函數(shù)外,就好像您MountainBike完全從頭開(kāi)始編寫(xiě)了一個(gè)新類,具有四個(gè)字段和五個(gè)方法。但是,您不必完成所有工作。如果Bicycle類中的方法很復(fù)雜并且調(diào)試花費(fèi)了大量時(shí)間,那么這將特別有價(jià)值。 您可以在子類中做什么 子類繼承其父級(jí)的所有公共成員和受保護(hù)成員,無(wú)論該子類位于哪個(gè)包中。如果該子類與其父級(jí)位于同一包中,則它還將繼承父級(jí)的包私有成員。您可以按原樣使用繼承的成員,替換它們,隱藏它們,或用新成員補(bǔ)充它們:
本課的以下各節(jié)將對(duì)這些主題進(jìn)行擴(kuò)展。 超類中的私人成員 子類不繼承private其父類的成員。但是,如果超類具有用于訪問(wèn)其私有字段的公共或受保護(hù)的方法,則子類也可以使用這些方法。 嵌套類可以訪問(wèn)其封閉類的所有私有成員,包括字段和方法。因此,子類繼承的公共或受保護(hù)的嵌套類可以間接訪問(wèn)超類的所有私有成員。 投射對(duì)象 我們已經(jīng)看到對(duì)象是實(shí)例化該類的數(shù)據(jù)類型的。例如,如果我們寫(xiě) public MountainBike myBike = new MountainBike(); 然后myBike是類型MountainBike。 MountainBike是Bicycle和的后代Object。因此,MountainBike是一個(gè)Bicycle,也是一個(gè)Object,并且它可用于任何Bicycle或Object對(duì)象被要求。 相反的情況不一定成立:aBicycle 可以是a MountainBike,但不一定。同樣,anObject 可以是aBicycle或a MountainBike,但不是必須的。 在繼承和實(shí)現(xiàn)允許的對(duì)象中,強(qiáng)制轉(zhuǎn)換顯示了使用一種類型的對(duì)象代替另一種類型的對(duì)象。例如,如果我們寫(xiě) Object obj = new MountainBike(); 然后obj是Object和兩者M(jìn)ountainBike(直到obj分配了另一個(gè)不是的對(duì)象的時(shí)間MountainBike)。這稱為隱式轉(zhuǎn)換。 另一方面,如果我們寫(xiě) MountainBike myBike = obj; 我們會(huì)得到一個(gè)編譯時(shí)錯(cuò)誤,因?yàn)閛bj編譯器不知道是MountainBike。但是,我們可以告訴編譯器,我們承諾分配MountainBike給obj通過(guò)顯式轉(zhuǎn)換: MountainBike myBike =(MountainBike)obj; 此強(qiáng)制轉(zhuǎn)換插入obj分配了的運(yùn)行時(shí)檢查,MountainBike以便編譯器可以安全地假定obj為MountainBike。如果obj不是MountainBike在運(yùn)行時(shí),則將引發(fā)異常。 注意: 您可以使用instanceof運(yùn)算符對(duì)特定對(duì)象的類型進(jìn)行邏輯測(cè)試。由于不正確的轉(zhuǎn)換,可以避免運(yùn)行時(shí)錯(cuò)誤。例如: if (obj instanceof MountainBike) { MountainBike myBike = (MountainBike)obj; } 在這里,instanceof操作員驗(yàn)證obj引用了a的內(nèi)容,MountainBike以便我們可以在不引發(fā)任何運(yùn)行時(shí)異常的情況下進(jìn)行強(qiáng)制轉(zhuǎn)換。 狀態(tài),實(shí)現(xiàn)和類型的多重繼承類和接口之間的一個(gè)重要區(qū)別是,類可以具有字段,而接口則不能。另外,您可以實(shí)例化一個(gè)類來(lái)創(chuàng)建對(duì)象,而這是無(wú)法使用接口完成的。如“什么是對(duì)象? ”部分中所述 。,對(duì)象將其狀態(tài)存儲(chǔ)在類中定義的字段中。Java編程語(yǔ)言不允許您擴(kuò)展多個(gè)類的原因之一是避免狀態(tài)的多重繼承問(wèn)題,這是從多個(gè)類繼承字段的能力。例如,假設(shè)您能夠定義一個(gè)擴(kuò)展多個(gè)類的新類。當(dāng)通過(guò)實(shí)例化該類創(chuàng)建對(duì)象時(shí),該對(duì)象將繼承該類所有超類的字段。如果來(lái)自不同超類的方法或構(gòu)造函數(shù)實(shí)例化同一字段怎么辦?哪個(gè)方法或構(gòu)造函數(shù)優(yōu)先?因?yàn)榻涌诓话侄危阅槐負(fù)?dān)心狀態(tài)的多重繼承引起的問(wèn)題。 實(shí)現(xiàn)的多重繼承是從多個(gè)類繼承方法定義的能力。這種類型的多重繼承會(huì)產(chǎn)生問(wèn)題,例如名稱沖突和歧義。當(dāng)支持這種多重繼承的編程語(yǔ)言的編譯器遇到包含具有相同名稱的方法的超類時(shí),它們有時(shí)無(wú)法確定要訪問(wèn)或調(diào)用的成員或方法。另外,程序員可以通過(guò)向超類添加新方法來(lái)無(wú)意間引入名稱沖突。 默認(rèn)方法介紹實(shí)現(xiàn)的多重繼承的一種形式。一個(gè)類可以實(shí)現(xiàn)多個(gè)接口,該接口可以包含具有相同名稱的默認(rèn)方法。Java編譯器提供了一些規(guī)則來(lái)確定特定類使用哪種默認(rèn)方法。 Java編程語(yǔ)言支持type的多重繼承,這是類實(shí)現(xiàn)多個(gè)接口的能力。一個(gè)對(duì)象可以具有多種類型:其自己的類的類型以及該類實(shí)現(xiàn)的所有接口的類型。這意味著,如果將變量聲明為接口的類型,則其值可以引用從實(shí)現(xiàn)該接口的任何類實(shí)例化的任何對(duì)象。在使用接口作為類型一節(jié)中對(duì)此進(jìn)行了討論 。 與實(shí)現(xiàn)的多重繼承一樣,類可以繼承其擴(kuò)展的接口中定義的方法的不同實(shí)現(xiàn)(默認(rèn)或靜態(tài))。在這種情況下,編譯器或用戶必須決定使用哪個(gè)。 覆蓋和隱藏方法 實(shí)例方法 子類中具有相同簽名(名稱,加上數(shù)字和參數(shù)的類型)且返回類型作為超類中的實(shí)例方法的實(shí)例方法將覆蓋超類的方法。 子類重寫(xiě)方法的能力使類可以從行為“足夠接近”的超類繼承,然后根據(jù)需要修改行為。覆蓋方法與其覆蓋的方法具有相同的名稱,數(shù)量和參數(shù)類型,并且返回類型相同。重寫(xiě)方法還可以返回重寫(xiě)方法返回的類型的子類型。此子類型稱為協(xié)變返回類型。 覆蓋方法時(shí),可能需要使用@Override注釋,該注釋指示編譯器打算覆蓋超類中的方法。如果由于某種原因,編譯器檢測(cè)到該方法在超類之一中不存在,則它將生成錯(cuò)誤。有關(guān)更多信息@Override,請(qǐng)參見(jiàn) Annotations。 靜態(tài)方法 如果子類定義的靜態(tài)方法具有與超類中的靜態(tài)方法相同的簽名,則子類中的方法會(huì)將其隱藏在超類中。 隱藏靜態(tài)方法和覆蓋實(shí)例方法之間的區(qū)別具有重要意義:
考慮一個(gè)包含兩個(gè)類的示例。第一個(gè)是Animal,其中包含一個(gè)實(shí)例方法和一個(gè)靜態(tài)方法: public class Animal { public static void testClassMethod() { System.out.println('The static method in Animal'); } public void testInstanceMethod() { System.out.println('The instance method in Animal'); } } 第二類是的子類Animal,稱為Cat: public class Cat extends Animal { public static void testClassMethod() { System.out.println('The static method in Cat'); } public void testInstanceMethod() { System.out.println('The instance method in Cat'); } public static void main(String[] args) { Cat myCat = new Cat(); Animal myAnimal = myCat; Animal.testClassMethod(); myAnimal.testInstanceMethod(); } } 該Cat班將覆蓋實(shí)例方法Animal,并隱藏了靜態(tài)方法Animal。main此類中的方法創(chuàng)建類的實(shí)例,Cat并testClassMethod()在類和實(shí)例上調(diào)用testInstanceMethod()。 該程序的輸出如下: The static method in Animal The instance method in Cat 如所承諾的,被調(diào)用的隱藏靜態(tài)方法的版本是超類中的版本,而被調(diào)用的重寫(xiě)實(shí)例方法的版本是子類中的版本。 接口方法 接口中的默認(rèn)方法和 抽象方法像實(shí)例方法一樣被繼承。但是,當(dāng)類或接口的超類型提供具有相同簽名的多個(gè)默認(rèn)方法時(shí),Java編譯器將遵循繼承規(guī)則來(lái)解決名稱沖突。這些規(guī)則由以下兩個(gè)原則驅(qū)動(dòng):
考慮以下類和接口: public class Horse { public String identifyMyself() { return 'I am a horse.'; } } public interface Flyer { default public String identifyMyself() { return 'I am able to fly.'; } } public interface Mythical { default public String identifyMyself() { return 'I am a mythical creature.'; } } public class Pegasus extends Horse implements Flyer, Mythical { public static void main(String... args) { Pegasus myApp = new Pegasus(); System.out.println(myApp.identifyMyself()); } } 該方法Pegasus.identifyMyself返回字符串I am a horse.
考慮以下接口和類: public interface Animal { default public String identifyMyself() { return 'I am an animal.'; } } public interface EggLayer extends Animal { default public String identifyMyself() { return 'I am able to lay eggs.'; } } public interface FireBreather extends Animal { } public class Dragon implements EggLayer, FireBreather { public static void main (String... args) { Dragon myApp = new Dragon(); System.out.println(myApp.identifyMyself()); } } 該方法Dragon.identifyMyself返回字符串I am able to lay eggs. 如果兩個(gè)或多個(gè)獨(dú)立定義的默認(rèn)方法發(fā)生沖突,或者默認(rèn)方法與抽象方法發(fā)生沖突,則Java編譯器將產(chǎn)生編譯器錯(cuò)誤。您必須顯式重寫(xiě)超類型方法。 考慮有關(guān)現(xiàn)在可以飛行的計(jì)算機(jī)控制汽車的示例。您有兩個(gè)接口(OperateCar和FlyCar)提供相同方法(startEngine)的默認(rèn)實(shí)現(xiàn): public interface OperateCar { // ... default public int startEngine(EncryptedKey key) { // Implementation } } public interface FlyCar { // ... default public int startEngine(EncryptedKey key) { // Implementation } } 同時(shí)實(shí)現(xiàn)這兩個(gè)方法OperateCar且FlyCar必須重寫(xiě)該方法的類startEngine。您可以使用super關(guān)鍵字調(diào)用任何默認(rèn)實(shí)現(xiàn)。 public class FlyingCar implements OperateCar, FlyCar { // ... public int startEngine(EncryptedKey key) { FlyCar.super.startEngine(key); OperateCar.super.startEngine(key); } } 前面的名稱super(在此示例中為FlyCar或OperateCar)必須引用直接超級(jí)接口,該超級(jí)接口定義或繼承了所調(diào)用方法的默認(rèn)值。這種形式的方法調(diào)用不限于區(qū)分包含相同簽名的默認(rèn)方法的多個(gè)已實(shí)現(xiàn)接口。您可以使用super關(guān)鍵字在類和接口中調(diào)用默認(rèn)方法。 從類繼承的實(shí)例方法可以覆蓋抽象接口方法??紤]以下接口和類: public interface Mammal { String identifyMyself(); } public class Horse { public String identifyMyself() { return 'I am a horse.'; } } public class Mustang extends Horse implements Mammal { public static void main(String... args) { Mustang myApp = new Mustang(); System.out.println(myApp.identifyMyself()); } } 該方法Mustang.identifyMyself返回字符串I am a horse.。類從該類Mustang繼承該方法identifyMyself,該類Horse將重寫(xiě)接口中同名的抽象方法Mammal。 注意:接口中的靜態(tài)方法永遠(yuǎn)不會(huì)被繼承。 修飾符 覆蓋方法的訪問(wèn)說(shuō)明符可以比覆蓋方法允許更多但不能更少的訪問(wèn)。例如,超類中的受保護(hù)實(shí)例方法可以在子類中公開(kāi),但不能私有。 如果嘗試將超類中的實(shí)例方法更改為子類中的靜態(tài)方法,并且相反,則將遇到編譯時(shí)錯(cuò)誤。 概括 下表總結(jié)了在定義具有與超類中的方法相同的簽名的方法時(shí)發(fā)生的情況。
注意: 在子類中,您可以重載從超類繼承的方法。這樣的重載方法既不會(huì)隱藏也不會(huì)覆蓋超類實(shí)例方法,它們是子類獨(dú)有的新方法。 多態(tài)性字典中的多態(tài)性定義是指生物學(xué)中的一種原理,其中生物或物種可以具有許多不同的形式或階段。該原理也可以應(yīng)用于面向?qū)ο蟮木幊毯蚃ava語(yǔ)言之類的語(yǔ)言。一個(gè)類的子類可以定義自己的獨(dú)特行為,但可以共享父類的某些相同功能。 可以通過(guò)對(duì)該Bicycle類進(jìn)行較小的修改來(lái)證明多態(tài)性。例如,printDescription可以將一個(gè)方法添加到類中,以顯示當(dāng)前存儲(chǔ)在實(shí)例中的所有數(shù)據(jù)。 public void printDescription(){ System.out.println('\nBike is ' + 'in gear ' + this.gear + ' with a cadence of ' + this.cadence + ' and travelling at a speed of ' + this.speed + '. '); } 要演示Java語(yǔ)言中的多態(tài)功能,請(qǐng)Bicycle使用MountainBike和擴(kuò)展RoadBike該類。為MountainBike,添加的字段suspension,該字段的String值指示自行車是否具有前減震器Front?;蛘撸孕熊嚲哂星昂鬁p震器Dual。 這是更新的類: public class MountainBike extends Bicycle { private String suspension; public MountainBike( int startCadence, int startSpeed, int startGear, String suspensionType){ super(startCadence, startSpeed, startGear); this.setSuspension(suspensionType); } public String getSuspension(){ return this.suspension; } public void setSuspension(String suspensionType) { this.suspension = suspensionType; } public void printDescription() { super.printDescription(); System.out.println('The ' + 'MountainBike has a' + getSuspension() + ' suspension.'); } } 請(qǐng)注意重寫(xiě)的printDescription方法。除了之前提供的信息外,有關(guān)懸架的其他數(shù)據(jù)也包含在輸出中。 接下來(lái),創(chuàng)建RoadBike類。由于公路或賽車自行車的輪胎很稀薄,因此請(qǐng)?zhí)砑右粋€(gè)屬性以跟蹤輪胎的寬度。這是RoadBike課程: public class RoadBike extends Bicycle{ // In millimeters (mm) private int tireWidth; public RoadBike(int startCadence, int startSpeed, int startGear, int newTireWidth){ super(startCadence, startSpeed, startGear); this.setTireWidth(newTireWidth); } public int getTireWidth(){ return this.tireWidth; } public void setTireWidth(int newTireWidth){ this.tireWidth = newTireWidth; } public void printDescription(){ super.printDescription(); System.out.println('The RoadBike' + ' has ' + getTireWidth() + ' MM tires.'); } } 請(qǐng)?jiān)俅巫⒁?,該printDescription方法已被覆蓋。這次,顯示有關(guān)輪胎寬度的信息。 總之,有三大類:Bicycle,MountainBike,和RoadBike。這兩個(gè)子類重寫(xiě)該printDescription方法并打印唯一信息。 這是一個(gè)創(chuàng)建三個(gè)Bicycle變量的測(cè)試程序。每個(gè)變量都分配給三個(gè)自行車類別之一。然后打印每個(gè)變量。 public class TestBikes { public static void main(String[] args){ Bicycle bike01, bike02, bike03; bike01 = new Bicycle(20, 10, 1); bike02 = new MountainBike(20, 10, 5, 'Dual'); bike03 = new RoadBike(40, 20, 8, 23); bike01.printDescription(); bike02.printDescription(); bike03.printDescription(); } } 以下是測(cè)試程序的輸出: 自行車的檔位為1,節(jié)奏為20,行駛速度為10。 自行車以20的節(jié)奏進(jìn)入5檔,并以10的速度行駛。 MountainBike具有雙重懸掛。 自行車的檔位為40,節(jié)奏為20,行駛速度為20。 RoadBike有23毫米的輪胎。 Java虛擬機(jī)(JVM)為每個(gè)變量中引用的對(duì)象調(diào)用適當(dāng)?shù)姆椒?。它不?huì)調(diào)用由變量類型定義的方法。此行為稱為虛擬方法調(diào)用,它說(shuō)明了Java語(yǔ)言中重要的多態(tài)性功能的一個(gè)方面。 隱藏字段在類中,與超類中的字段具有相同名稱的字段將隱藏超類的字段,即使它們的類型不同。在子類中,不能通過(guò)其簡(jiǎn)單名稱引用超類中的字段。而是必須通過(guò)super下一節(jié)中介紹的來(lái)訪問(wèn)該字段。一般來(lái)說(shuō),我們不建議隱藏字段,因?yàn)檫@會(huì)使代碼難以閱讀。 使用超級(jí)關(guān)鍵字訪問(wèn)父類成員如果您的方法覆蓋了其超類的方法之一,則可以使用關(guān)鍵字調(diào)用被覆蓋的方法super。您也可以使用它super來(lái)引用隱藏字段(盡管不建議使用隱藏字段)。考慮這個(gè)類Superclass: public class Superclass { public void printMethod() { System.out.println('Printed in Superclass.'); } } 這是一個(gè)名為的子類,該子類Subclass將覆蓋printMethod(): public class Subclass extends Superclass { // overrides printMethod in Superclass public void printMethod() { super.printMethod(); System.out.println('Printed in Subclass'); } public static void main(String[] args) { Subclass s = new Subclass(); s.printMethod(); } } 在其中Subclass,簡(jiǎn)單名稱printMethod()指的是在中聲明的名稱,而在中聲明的名稱將被Subclass覆蓋Superclass。因此,要引用printMethod()繼承自Superclass,Subclass必須使用限定名稱,super如下所示。編譯并執(zhí)行將Subclass打印以下內(nèi)容: Printed in Superclass. Printed in Subclass 子類構(gòu)造函數(shù)下面的示例說(shuō)明如何使用super關(guān)鍵字來(lái)調(diào)用超類的構(gòu)造函數(shù)?;叵胍幌略?Bicycle 示例MountainBike的一個(gè)子類Bicycle。以下是MountainBike(子類)構(gòu)造函數(shù),該構(gòu)造函數(shù)調(diào)用超類構(gòu)造函數(shù),然后添加其自身的初始化代碼: public MountainBike(int startHeight, int startCadence, int startSpeed, int startGear) { super(startCadence, startSpeed, startGear); seatHeight = startHeight; } 父類構(gòu)造函數(shù)的調(diào)用必須是子類構(gòu)造函數(shù)的第一行。 調(diào)用超類構(gòu)造函數(shù)的語(yǔ)法是 super(); 或者: super(parameter list); 使用super(),將調(diào)用超類無(wú)參數(shù)構(gòu)造函數(shù)。使用super(parameter list),將調(diào)用具有匹配參數(shù)列表的超類構(gòu)造函數(shù)。 注意: 如果構(gòu)造函數(shù)未顯式調(diào)用超類構(gòu)造函數(shù),則Java編譯器會(huì)自動(dòng)將調(diào)用插入到超類的無(wú)參數(shù)構(gòu)造函數(shù)中。如果超類沒(méi)有無(wú)參數(shù)構(gòu)造函數(shù),則會(huì)出現(xiàn)編譯時(shí)錯(cuò)誤。Object 確實(shí)有這樣的構(gòu)造函數(shù),所以如果Object是唯一的超類,就沒(méi)有問(wèn)題。 如果子類構(gòu)造函數(shù)顯式或隱式調(diào)用其超類的構(gòu)造函數(shù),則您可能會(huì)認(rèn)為將調(diào)用整個(gè)構(gòu)造函數(shù)鏈,一直返回到的構(gòu)造函數(shù)Object。實(shí)際上就是這種情況。這稱為構(gòu)造函數(shù)鏈接,當(dāng)有很長(zhǎng)的一類類下降時(shí),您需要意識(shí)到這一點(diǎn)。 作為超類的對(duì)象 該 Object類,在java.lang包裝,坐鎮(zhèn)類層次結(jié)構(gòu)樹(shù)的頂端。每個(gè)類都是該類的直接或間接后代Object。您使用或編寫(xiě)的每個(gè)類都繼承的實(shí)例方法Object。您不需要使用任何這些方法,但是,如果您選擇使用這些方法,則可能需要使用特定于您的類的代碼來(lái)覆蓋它們。Object本節(jié)將討論從中繼承的方法:
的notify,notifyAll和wait方法Object都在一個(gè)程序,它在后面的課程中討論,并不會(huì)在這里介紹的同步獨(dú)立運(yùn)行的線程的活動(dòng)中發(fā)揮作用。這些方法有五種:
注意: 這些方法,尤其是方法,在某些方面有一些微妙的方面clone。 clone()方法 如果一個(gè)類或其超類之一實(shí)現(xiàn)了Cloneable接口,則可以使用該clone()方法從現(xiàn)有對(duì)象創(chuàng)建副本。要?jiǎng)?chuàng)建克隆,請(qǐng)編寫(xiě): aCloneableObject .clone(); Object此方法的實(shí)現(xiàn)檢查是否clone()調(diào)用了其的對(duì)象實(shí)現(xiàn)了該Cloneable接口。如果對(duì)象不存在,則該方法將引發(fā)CloneNotSupportedException異常。異常處理將在以后的課程中介紹。目前,您需要知道clone()必須聲明為 protected Object clone() throws CloneNotSupportedException 或者: public Object clone() throws CloneNotSupportedException 如果您要編寫(xiě)一種clone()方法來(lái)覆蓋中的方法Object。 如果clone()調(diào)用了該對(duì)象的對(duì)象確實(shí)實(shí)現(xiàn)了該Cloneable接口,則Object該clone()方法的實(shí)現(xiàn)將創(chuàng)建一個(gè)與原始對(duì)象相同類的對(duì)象,并將新對(duì)象的成員變量初始化為具有與原始對(duì)象的相應(yīng)成員變量相同的值。 使您的類可克隆的最簡(jiǎn)單方法是添加implements Cloneable到類的聲明中。然后您的對(duì)象可以調(diào)用該clone()方法。 對(duì)于一些類,默認(rèn)行為Object的clone()方法工作得很好。但是,如果對(duì)象包含對(duì)外部對(duì)象的引用(例如)ObjExternal,則可能需要重寫(xiě)clone()以獲取正確的行為。否則,ObjExternal一個(gè)對(duì)象所做的更改也將在其克隆中可見(jiàn)。這意味著原始對(duì)象及其克隆不是獨(dú)立的,clone()要使它們分離,必須重寫(xiě)以克隆對(duì)象和 ObjExternal。然后,原始對(duì)象引用ObjExternal和克隆引用的克隆ObjExternal,從而使對(duì)象及其克隆真正獨(dú)立。 equals()方法 該equals()方法比較兩個(gè)對(duì)象是否相等,true如果相等則返回。該類中equals()提供的方法Object使用身份運(yùn)算符(==)確定兩個(gè)對(duì)象是否相等。對(duì)于原始數(shù)據(jù)類型,這將給出正確的結(jié)果。但是,對(duì)于對(duì)象,則不是。所equals()提供的方法Object測(cè)試對(duì)象引用是否相等,即所比較的對(duì)象是否是完全相同的對(duì)象。 要測(cè)試兩個(gè)對(duì)象在等效性上是否相等(包含相同的信息),必須重寫(xiě)該equals()方法。這是Book重寫(xiě)的類的示例equals(): public class Book { ... public boolean equals(Object obj) { if (obj instanceof Book) return ISBN.equals((Book)obj.getISBN()); else return false; } } 考慮以下代碼,該代碼測(cè)試Book該類的兩個(gè)實(shí)例是否相等: // Swing Tutorial, 2nd edition Book firstBook = new Book('0201914670'); Book secondBook = new Book('0201914670'); if (firstBook.equals(secondBook)) { System.out.println('objects are equal'); } else { System.out.println('objects are not equal'); } 該程序objects are equal即使顯示firstBook并secondBook引用兩個(gè)不同的對(duì)象。之所以認(rèn)為它們相等,是因?yàn)樗容^的對(duì)象包含相同的ISBN號(hào)。 equals()如果身份運(yùn)算符不適合您的類,則應(yīng)始終覆蓋該方法。 注意: 如果您覆蓋equals(),則還必須覆蓋hashCode()。 finalize()方法 的Object類提供的回調(diào)方法,finalize()即可以在物體上時(shí),它變?yōu)槔{(diào)用。Object的實(shí)現(xiàn)finalize()不執(zhí)行任何操作-您可以重寫(xiě)finalize()以進(jìn)行清理,例如釋放資源。 該finalize()方法可以由系統(tǒng)自動(dòng)調(diào)用,但是何時(shí)調(diào)用,即使調(diào)用,也不確定。因此,您不應(yīng)依賴此方法為您進(jìn)行清理。例如,如果您在執(zhí)行I / O之后沒(méi)有在代碼中關(guān)閉文件描述符,而您希望finalize()為您關(guān)閉它們,則文件描述符可能用完了。 getClass()方法 您不能覆蓋getClass。 該getClass()方法返回一個(gè)Class對(duì)象,該對(duì)象具有可用于獲取有關(guān)類的信息的方法,例如其名稱(getSimpleName()),其超類(getSuperclass())及其實(shí)現(xiàn)的接口(getInterfaces())。例如,以下方法獲取并顯示對(duì)象的類名稱: void printClassName(Object obj) { System.out.println('The object's' + ' class is ' + obj.getClass().getSimpleName()); } 程序包中的 Class類java.lang具有大量方法(超過(guò)50種)。例如,您可以測(cè)試該類是注解(isAnnotation()),接口(isInterface())還是枚舉(isEnum())。您可以看到對(duì)象的字段是(getFields())或?qū)ο蟮姆椒ㄊ牵╣etMethods()),依此類推。 hashCode()方法 返回的值hashCode()是對(duì)象的哈希碼,它是對(duì)象的內(nèi)存地址(十六進(jìn)制)。 根據(jù)定義,如果兩個(gè)對(duì)象相等,則它們的哈希碼也必須相等。如果您重寫(xiě)此equals()方法,則會(huì)更改兩個(gè)對(duì)象的相等方式,并且Object的實(shí)現(xiàn)hashCode()不再有效。因此,如果您覆蓋該equals()方法,則還必須覆蓋該hashCode()方法。 toString()方法 您應(yīng)該始終考慮toString()在類中重寫(xiě)該方法。 所述Object的toString()方法返回String的對(duì)象,這是對(duì)于調(diào)試是非常有用的表示。String對(duì)象的表示形式完全取決于對(duì)象,這就是為什么需要toString()在類中進(jìn)行覆蓋的原因。 您可以結(jié)合使用toString()和System.out.println()來(lái)顯示對(duì)象的文本表示形式,例如的實(shí)例Book: System.out.println(firstBook.toString()); 對(duì)于正確覆蓋的toString()方法,它將打印出有用的內(nèi)容,例如: 國(guó)際標(biāo)準(zhǔn)書(shū)號(hào)(ISBN):0201914670;Swing教程;GUI構(gòu)造指南,第二版 編寫(xiě)Final類和方法您可以將某些或所有類的方法聲明為final。您可以final在方法聲明中使用關(guān)鍵字來(lái)指示該方法不能被子類覆蓋。該Object班做這個(gè),它的一些方法final。 如果某個(gè)方法的實(shí)現(xiàn)不應(yīng)該更改并且對(duì)于對(duì)象的一致?tīng)顟B(tài)至關(guān)重要,則可能希望將其定型。例如,您可能想使getFirstPlayer此類中的方法成為ChessAlgorithmfinal: class ChessAlgorithm { enum ChessPlayer { WHITE, BLACK } ... final ChessPlayer getFirstPlayer() { return ChessPlayer.WHITE; } ... } 從構(gòu)造函數(shù)調(diào)用的方法通常應(yīng)聲明為final。如果構(gòu)造函數(shù)調(diào)用了非最終方法,則子類可能會(huì)重新定義該方法,從而產(chǎn)生令人驚訝或不良的結(jié)果。 注意,您也可以聲明整個(gè)類的final。聲明為final的類不能被子類化。例如,當(dāng)創(chuàng)建一個(gè)不可變的類(例如String該類)時(shí),這特別有用 抽象方法和類 一個(gè)抽象類是聲明的類abstract-它可能會(huì)或可能不包括抽象方法。抽象類不能被實(shí)例化,但是可以被子類化。 一個(gè)抽象方法是沒(méi)有實(shí)現(xiàn)聲明(沒(méi)有括號(hào),并且隨后是分號(hào)),像這樣的方法: abstract void moveTo(double deltaX,double deltaY); 如果一個(gè)類包含抽象方法,則必須聲明該類本身abstract,如: public abstract class GraphicObject { // declare fields // declare nonabstract methods abstract void draw(); } 當(dāng)抽象類被子類化時(shí),該子類通常為其父類中的所有抽象方法提供實(shí)現(xiàn)。但是,如果沒(méi)有,則還必須聲明子類abstract。 注: 方法在接口(見(jiàn) 接口部分)未聲明為默認(rèn)或靜態(tài)的含蓄抽象的,所以abstract修改不與接口方法使用。(可以使用,但這不是必需的。) 抽象類與接口的比較 抽象類類似于接口。您無(wú)法實(shí)例化它們,它們可能包含使用或不使用實(shí)現(xiàn)聲明的方法的混合。但是,使用抽象類,您可以聲明非靜態(tài)和最終字段,并定義公共,受保護(hù)的和私有的具體方法。使用接口時(shí),所有字段都自動(dòng)是公共的,靜態(tài)的和最終的,并且您聲明或定義的所有方法(作為默認(rèn)方法)都是公共的。此外,無(wú)論是否抽象,您都只能擴(kuò)展一個(gè)類,而您可以實(shí)現(xiàn)任意數(shù)量的接口。 您應(yīng)該使用哪個(gè)抽象類或接口?
JDK中的抽象類的一個(gè)示例是 AbstractMap,它是Collections Framework的一部分。它的子類(包括HashMap,TreeMap,和ConcurrentHashMap)共享許多方法(包括get,put,isEmpty,containsKey,和containsValue),其AbstractMap定義。 在JDK的類的例子,它實(shí)現(xiàn)幾個(gè)接口是 HashMap,它實(shí)現(xiàn)了接口Serializable,Cloneable和Map<K, V>。通過(guò)閱讀此接口列表,您可以推斷出HashMap(可以實(shí)例化該類的開(kāi)發(fā)人員或公司的)實(shí)例可以被克隆,并且可以序列化(這意味著可以將其轉(zhuǎn)換為字節(jié)流;請(qǐng)參見(jiàn)“序列化對(duì)象”部分)。 ),并具有地圖功能。此外,該Map<K, V>接口已通過(guò)許多默認(rèn)方法進(jìn)行了增強(qiáng),例如,merge并且forEach不必定義實(shí)現(xiàn)此接口的較早的類。 注意,許多軟件庫(kù)同時(shí)使用抽象類和接口。在HashMap類實(shí)現(xiàn)多個(gè)接口,并且還擴(kuò)展了抽象類AbstractMap。 一個(gè)抽象類的例子 在面向?qū)ο蟮睦L圖應(yīng)用程序中,您可以繪制圓形,矩形,直線,貝塞爾曲線和許多其他圖形對(duì)象。這些對(duì)象都有共同的某些狀態(tài)(例如:位置,方向,線條顏色,填充顏色)和行為(例如:moveTo,旋轉(zhuǎn),調(diào)整大小,繪制)。所有圖形對(duì)象的某些狀態(tài)和行為都是相同的(例如:position,fill color和moveTo)。其他的則需要不同的實(shí)現(xiàn)(例如,調(diào)整大小或繪制)。所有GraphicObject的人都必須能夠畫(huà)畫(huà)或調(diào)整自己的大小。他們只是在做事方式上有所不同。對(duì)于抽象超類來(lái)說(shuō),這是一個(gè)完美的情況。您可以利用相似之處,并聲明所有圖形對(duì)象都可以從同一個(gè)抽象父對(duì)象(例如GraphicObject)繼承,如圖所示。 下圖。 從GraphicObject繼承的Rectangle,Line,Bezier和Circle類 首先,您聲明一個(gè)抽象類,GraphicObject以提供由所有子類完全共享的成員變量和方法,例如當(dāng)前位置和moveTo方法。GraphicObject還為方法(例如draw或)聲明了抽象方法,這些方法resize需要由所有子類實(shí)現(xiàn),但必須以不同的方式實(shí)現(xiàn)。本GraphicObject類可以是這個(gè)樣子: abstract class GraphicObject { int x, y; ... void moveTo(int newX, int newY) { ... } abstract void draw(); abstract void resize(); } 的每個(gè)非抽象子類GraphicObject,例如Circle和Rectangle,必須提供draw和resize方法的實(shí)現(xiàn): class Circle extends GraphicObject { void draw() { ... } void resize() { ... } } class Rectangle extends GraphicObject { void draw() { ... } void resize() { ... } } 當(dāng)抽象類實(shí)現(xiàn)接口時(shí) 在上的小節(jié)中 Interfaces,指出實(shí)現(xiàn)接口的類必須實(shí)現(xiàn)接口的所有方法。但是,可以定義一個(gè)不實(shí)現(xiàn)接口所有方法的類,前提是該類聲明為abstract。例如, abstract class X implements Y { // implements all but one method of Y } class XX extends X { // implements the remaining method in Y } 在這種情況下,classX必須是abstract因?yàn)樗鼪](méi)有完全實(shí)現(xiàn)Y,但是classXX實(shí)際上卻實(shí)現(xiàn)了Y。 類成員 抽象類可能具有static字段和static方法。您可以AbstractClass.staticMethod()像使用任何其他類一樣將這些靜態(tài)成員與類引用一起使用(例如)。 繼承摘要除了Object該類外,一類僅具有一個(gè)直接的超類。一個(gè)類從其所有超類(無(wú)論是直接的還是間接的)繼承字段和方法。子類可以覆蓋其繼承的方法,也可以隱藏其繼承的字段或方法。(請(qǐng)注意,隱藏字段通常是不好的編程習(xí)慣。) “覆蓋和隱藏方法”部分中的表 顯示了聲明與超類中的方法具有相同簽名的方法的效果。 本Object類是類層次結(jié)構(gòu)的頂部。所有類都是該類的后代,并從該類繼承方法。從繼承的有用的方法Object包括toString(),equals(),clone(),和getClass()。 您可以通過(guò)final在類的聲明中使用關(guān)鍵字來(lái)防止類被子類化。同樣,可以通過(guò)將方法聲明為最終方法來(lái)防止方法被子類覆蓋。 抽象類只能被子類化。它不能被實(shí)例化。抽象類可以包含抽象方法,即已聲明但未實(shí)現(xiàn)的方法。然后,子類提供抽象方法的實(shí)現(xiàn)。 問(wèn)題與練習(xí):繼承問(wèn)題1.考慮以下兩類: public class ClassA { public void methodOne(int i) { } public void methodTwo(int i) { } public static void methodThree(int i) { } public static void methodFour(int i) { } } public class ClassB extends ClassA { public static void methodOne(int i) { } public void methodTwo(int i) { } public void methodThree(int i) { } public static void methodFour(int i) { } } 一個(gè)。哪個(gè)方法會(huì)覆蓋超類中的方法? |
|