Java编程思想 学习笔记7
七、復用類
?
1.組合語法?
在新的類中產生現有類的對象。由于新的類是由現有類的對象所組成,所以這種方法叫做組合。
類中域為基本類型時能夠自動被初始化為零。對象引用被初始化為null。
編譯器不是簡單地為每一個引用都創建默認對象,如果想初始化這些引用,可以在代碼中的下列位置進行:
1.在定義對象的地方。這意味著它們總是能夠在構造器被調用之前被初始化。
2.在類的構造器中。
3.就在正要使用這些對象之前,這種方式稱為惰性初始化。
4.使用實例初始化。
2.繼承語法?
當創建一個類時,總是在繼承,因此,除非已明確指出要從其他類中繼承,否則就是在隱式地從Java的標準根類Object進行繼承。
為了繼承,一般的規則是將所有的數據成員都指定為private,將所有的方法都指定為public。
Java用super關鍵字表示超類的意思,當前類就是從超類繼承來的。
①初始化基類
從外部看,導出類就像是一個與基類具有相同接口的新類,或許還會有一些額外的方法和域。但繼承并不只是復制基類的接口。當創建了一個導出類的對象時,該對象包含了一個基類的子對象。這個子對象與你用基類直接創建的對象是一樣的。二者區別在于,后者來自于外部,而基類的子對象被包裝在導出類的內部。
對基類子對象的正確初始化也是至關重要的,而且僅有一種方法來保證這一點:構造器中調用基類構造器來執行初始化。Java會自動在導出類的構造器中插入對基類構造器的調用。
但是,如果沒有默認的基類構造器,或者想調用一個帶參數的基類構造器,就必須用關鍵字super顯式地編寫調用基類構造器的語句,并且配以適當的參數列表。
調用基類構造器必須是你在導出類構造器中要做的第一件事。
3.代理?
第三種關系稱為代理,Java并沒有提供對它的直接支持。這是繼承和組合的中庸之道,因為我們將一個成員對象置于所要構造的類中(就像組合),但與此同時我們在新類中暴露了該成員對象的所有方法(就像繼承)。例如,太空船需要一個控制模塊:
public class SpaceShipControls {void up(int velocity) {}void down(int velocity) {}void left(int velocity) {}void right(int velocity) {} }構造太空船的一種方式是使用繼承:
public class SpaceShip extends SpaceShipControls {private String name;public SpaceShip(String name) {this.name = name;}public static void main(String[] args) {SpaceShip protector = new SpaceShip("NASA");protector.up(100);} }然而,SpaceShip并非真正的SpaceShipControls類型。更準確地說,SpaceShip包含了SpaceShipControls,與此同時,SpaceShipControls的所有方法在SpaceShip中都暴露了出來。代理解決了此難題:
public class SpaceShipDelegation {private String name;private SpaceShipControls controls =new SpaceShipControls();public SpaceShipDelegation(String name) {this.name = name;}// Delegation methodspublic void back(int velocity) {controls.back(velocity);}public void up(int velocity) {controls.up(velocity);}...public static void main(String[] args) {SpaceShipDelegation protector =new SpaceShipDelegation("NASA");protector.up(100);} }4.結合使用組合和繼承?
名稱屏蔽
如果Java的基類擁有某個已被多次重載的方法的名稱,那么在導出類中重新定義該方法名稱并不會屏蔽其在基類中的任何版本。因此,無論是在該層或者它的基類中對方法進行定義,重載機制都可以正常工作。
Java SE5新增加了@Override注解,它并不是關鍵字,但是可以把它當作關鍵字使用。當你想要覆寫某個方法時,可以選擇添加這個注解,在你不留心重載而并非覆寫了該方法時,編譯器就會生成一條錯誤消息。
5.在組合與繼承之間選擇?
組合技術通常用于想在新類中使用現有類的功能而非它的接口這種情形。即,在新類中嵌入某個對象,讓其實現所需要的功能,但新類的用戶看到的只是為新類所定義的接口,而非所嵌入對象的接口。
在繼承的時候,使用某個現有類,并開發一個它的特殊版本。通常,這意味著你在使用一個通用類,并為了某種特殊需要而將其特殊化。
思考一下,用一個“交通工具”對象來構成一部“車子”是毫無意義的,因為“車子”并不包含“交通工具”,它僅是一種交通工具(is-a關系)。“is-a"(是一個)關系是用繼承來表達的,而“has-a”(有一個)的關系是用組合來表達的。
6.protected關鍵字?
protected,它指明“就類用戶而言,這是private的,但對于任何繼承于此類的導出類或其他任何位于同一個包內的類來說,它卻是可以訪問的。”
盡管可以創建protected域,但是最好的方式還是將域保持為private;你應當一直保留“更改底層實現”的權利。然后通過protected方法來控制類的繼承者的訪問權限。
7.向上轉型?
“向新的類提供方法”并不是繼承技術中最重要的方面,其最重要的方面是用來表現新類和基類之間的關系。這種關系可以用“新類是現有類的一種類型”這句話加以概括。
由于繼承可以確保基類中所有的方法導出類中也同樣有效,所以能夠向基類發送的所以信息同樣也可以向導出類發送。將導出類引用轉換為基類引用的動作,我們稱之為向上轉型。
由于向上轉型是從一個較專用類型向較通用類型轉換,所以總是很安全的。在向上轉型的過程中,類接口唯一可能發生的事情是丟失方法。
到底是該用組合還是繼承,一個最清晰的判斷方法就是問一問自己是否需要從新類向基類進行向上轉型。如果必須向上轉型,則繼承是必要的。
8.final關鍵字?
①final數據
許多編程語言都有某種方法,來向編譯器告知一塊數據是恒定不變的。有時數據的恒定不變是很有用的,例如:
1.一個永不改變的編譯時常量
2.一個在運行時被初始化的值,而你不希望它被改變。
在Java中,編譯時常量必須時基本數據類型,并且以關鍵字final表示。在對這個常量進行定義的時候,必須對其進行賦值。
一個既是static又是final的域只占據一段不能改變的存儲空間。根據慣例,既是static又是final的域將用大寫表示,并使用下劃線分隔各個單詞。
對于對象引用,final使其引用恒定不變。然而對象本身卻是可以改變的,Java并未提供使任何對象恒定不變的途徑。
不能因為某數據是final的就認為在編譯時可以知道它的值。
Java允許生成“空白final”,所謂空白final是指被聲明為final但又未給定初值的域。無論什么情況,編譯器都確保空白final在使用前都必須被初始化。但是,空白final在關鍵字final的使用上提供了更大的靈活性,為此,一個類中的final域就可以做到根據對象而有所不同,卻又保持恒定不變的特性。
Java允許在參數列表中以聲明的方式將參數指明為final。這意味著你無法在方法中更改參數引用所指向的對象。這一特性主要用來向匿名內部類傳遞數據。
②final方法
使用final方法的原因有兩個。第一個原因是把方法鎖定,以防任何繼承類修改它的含義。這是出于設計的考慮:想要確保在繼承中使方法行為保持不變,并且不會被覆蓋。過去建議使用final方法的第二個原因是效率。在Java的早期實現中,如果將一個方法指明為final,就是同意編譯器將針對該方法的所有調都轉為內嵌調用。如今不需要用final來進行效率優化了。
類中的所有的private方法都隱式地指定為是final的。
“覆蓋”只有在某方法是基類的接口的一部分時才會出現。即,必須能將一個對象向上轉型為它的基本類型并調用相同的方法。如果某方法為private,它就不是基類接口的一部分。它僅是一些隱藏于類中的程序代碼,只不過時具有相同的名稱而已。但如果在導出類中以相同的名稱生成一個public、protected或包訪問權限方法的話,該方法就不會產生在基類中出現的“僅具有相同名稱”的情況。此時你并沒有覆蓋該方法,僅是生成了一個新的方法。由于private方法無法觸及而且能有效隱藏,所有除了把它所歸屬的類的組織結構的原因而存在外,其他任何事物都不需要考慮到它。
③final類
當將某個類的整體定義為final時,就表明了你不打算繼承該類,而且也不允許別人這樣做。
由于final類禁止繼承,所以final類中所有的方法都隱式指定為是final的,因為無法覆蓋它們。
9.初始化及類的加載?
Java中所有事物都是對象。每個類的編譯代碼都存在于它自己的獨立的文件中,該文件只在需要使用程序代碼時才會被加載。一般來說,可以說:“類的代碼在初次使用時才加載。”這通常是指加載發生于創建類的第一個對象之時,但是當訪問static域或static方法時,也會發生加載。(構造器也是static方法。因此更準確地說,類是在任何static成員被訪問時加載的。)
繼承與初始化
了解包括繼承在內的初始化全過程,以對所發生的一切有個全局的把握,是很有益的。看下例:
class Insect {private int i = 9;protected int j;Insect() {System.out.println("i = "+i+", j = "+j);j=39;}private static int x1 = printInit("static Insect.x1 initialized");static int printInit(String s) {System.out.println(s);return 47;} }public class Beetle extends Insect {private int k = printInit("Beetle.k initialized");public Beetle() {System.out.println("k = "+k);System.out.println("j = "+j);}private static int x2 = printInit("static Beetle.x2 initialized");public static void main(String[] args) {System.out.println("Beetle constrictor"); Beetle b = new Beetle();} }/**Output static Insect.x1 initialized static Beetle.x2 initialized Beetle constrictor i = 9, j = 0 Beetle.k initialized k = 47 j = 39 */?
在Java上運行Beetle時,第一件事就是試圖訪問Bettle.main()(一個static方法),于是加載器開始啟動并找出Beetle類的編譯代碼(在名為Beetle.class的文件中)。在加載過程中,編譯器注意到它有一個基類(由extends),于是它繼續進行加載。不管是否打算創建一個該基類的對象,這都要發生。
如果該基類還有自身的基類,那么第二個基類就會被加載,如此類推。接下來,根基類中的static初始化(此例中是Insect)即會被執行,然后是下一個導出類,以此類推。這種方式很重要,因為導出類的static初始化可能會依賴于基類成員能否被正確初始化。
至此為止,必要的類都已加載完畢,對象就可以創建了。首先,對象中的所有基本類型都會被設為默認值、對象引用被設為null——這是通過將對象內存設為二進制零值而一舉生成的。然后,基類的構造器會被調用。在基類構造器完成以后,實例變量按其次序被初始化。最后,構造器的其余部分將執行。
10.總結?
繼承和組合都能從現有類型生成新類型。組合一般是將現有類型作為新類型底層實現的一部分來加以復用,而繼承復用的是接口。
盡管面向對象編程對繼承極力強調,但在開始一個設計時,一般應優先選擇使用組合(或者可能是代理),只在確實必要時才使用繼承。
?
轉載于:https://www.cnblogs.com/fht-litost/p/8388186.html
總結
以上是生活随笔為你收集整理的Java编程思想 学习笔记7的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 图像平滑与滤波
- 下一篇: Java中,内部类的概述和内部类的访问特