think in java中的初始化,final,static,繼承

來源:互聯網
上載者:User

2.6.3 static關鍵字
通常,我們建立類時會指出那個類的對象的外觀與行為。除非用new建立那個類的一個對象,
否則實際上並未得到任何東西。只有執行了new後,才會正式產生資料存放區空間,並可使用相應的方法。
但在兩種特殊的情形下,上述方法並不堪用。
一種情形是只想用一個儲存地區來儲存一個特定的資料——無論要建立多少個對象,
甚至根本不建立對象。另一種情形是我們需要一個特殊的方法,它沒有與這個類的任何對象關聯。
也就是說,即使沒有建立對象,也需要一個能調用的方法。為滿足這兩方面的要求,可使用static(靜態)關鍵字。
一旦將什麼東西設為static,資料或方法就不會同那個類的任何對象執行個體聯絡到一起。
所以儘管從未建立那個類的一個對象,仍能調用一個static方法,或訪問一些static資料。
而在這之前,對於非static資料和方法,我們必須建立一個對象,並用那個對象訪問資料或方法。
這是由於非static資料和方法必須知道它們操作的具體對象。當然,在正式使用前,
由於static方法不需要建立任何對象,所以它們不可簡單地調用其他那些成員,同時不引用一個已命名的對象,
從而直接存取非static成員或方法(因為非static成員和方法必須同一個特定的對象關聯到一起)。
有些物件導向的語言使用了“類資料”和“類方法”這兩個術語。
它們意味著資料和方法只是為作為一個整體的類而存在的,並不是為那個類的任何特定對象。
有時,您會在其他一些Java書刊裡發現這樣的稱呼。
為了將資料成員或方法設為static,只需在定義前置和這個關鍵字即可。
-----------------------------------------------------------------------------------------------------

1. 在建構函式中可以調用其它的建構函式,必須在開始首先調用,再去初始化其實變數。也不能調用兩個以上的constructor
2. 4.4 成員初始化
    4.4.1 規定初始化
    4.4.2 構建器初始化
---------------------------------------------------------------------------------------------
1. 初始化順序
在一個類裡,初始化的順序是由變數在類內的定義順序決定的。即使變數定義大量遍佈於方法定義的中間,
那些變數仍會在調用任何方法之前得到初始化——甚至在構建器調用之前。

2. 待用資料的初始化
若資料是靜態(static),那麼同樣的事情就會發生;
如果它屬於一個基本類型(主類型),而且未對其初始化,就會自動獲得自己的標準基本類型初始值;
如果它是指向一個對象的控制代碼,那麼除非建立一個對象,並將控制代碼同它串連起來,否則就會得到一個空值(NULL)。
如果想在定義的同時進行初始化,採取的方法與非靜態值表面看起來是相同的。
但由於static值只有一個儲存地區,所以無論建立多少個對象,
都必然會遇到何時對那個儲存地區進行初始化的問題。

static初始化只有在必要的時候才會進行
初始化的順序是首先static(如果它們尚未由前一次對象建立過程初始化),接著是非static對象。

在這裡有必要總結一下對象的建立過程。請考慮一個名為Dog的類:
(1) 類型為Dog的一個對象首次建立時,或者Dog類的static方法/static欄位首次訪問時,Java解譯器必須找到Dog.class(在事先設好的類路徑裡搜尋)。
(2) 找到Dog.class後(它會建立一個Class對象,這將在後面學到),它的所有static初始化模組都會運行。因此,static初始化僅發生一次——在Class對象首次載入的時候。
(3) 建立一個new Dog()時,Dog對象的構建進程首先會在記憶體堆(Heap)裡為一個Dog對象分配足夠多的儲存空間。
(4) 這種儲存空間會清為零,將Dog中的所有基本類型設為它們的預設值(零用於數字,以及boolean和char的等價設定)。
(5) 進列欄位定義時發生的所有初始化都會執行。
(6) 執行構建器。正如第6章將要講到的那樣,這實際可能要求進行相當多的操作,特別是在涉及繼承的時候。

3. 明確進行的靜態初始化
Java允許我們將其他static初始化工作劃分到類內一個特殊的“static構建從句”(有時也叫作“靜態塊”)裡。
它看起來象下面這個樣子:

class Spoon {
static int i;
static {
    i = 47;
}

4. 非靜態執行個體的初始化
針對每個對象的非靜態變數的初始化,Java 1.1提供了一種類似的文法格式。

---------------------------------------------------------------------------------------------

如希望控制代碼得到初始化,可在下面這些地方進行:
(1) 在對象定義的時候。這意味著它們在構建器調用之前肯定能得到初始化。
(2) 在那個類的構建器中。
(3) 緊靠在要求實際使用那個對象之前。這樣做可減少不必要的開銷——假如對象並不需要建立的話。

6.9 初始化和類裝載
在許多傳統語言裡,程式都是作為啟動過程的一部分一次性載入的。隨後進行的是初始化,再是正式執行程式。
在這些語言中,必須對初始化過程進行謹慎的控制,保證static資料的初始化不會帶來麻煩。
比如在一個static資料獲得初始化之前,就有另一個static資料希望它是一個有效值,那麼在C++中就會造成問題。
Java則沒有這樣的問題,因為它採用了不同的裝載方法。由於Java中的一切東西都是對象,
所以許多活動變得更加簡單,這個問題便是其中的一例。正如下一章會講到的那樣,
每個對象的代碼都存在於獨立的檔案中。除非真的需要代碼,否則那個檔案是不會載入的。
通常,我們可認為除非那個類的一個物件建構完畢,否則代碼不會真的載入。
由於static方法存在一些細微的歧義,所以也能認為“類代碼在首次使用的時候載入”。
首次使用的地方也是static初始化發生的地方。裝載的時候,
所有static對象和static代碼塊都會按照本來的順序初始化(亦即它們在類定義代碼裡寫入的順序)。
當然,static資料只會初始化一次。

6.9.1 繼承初始化
我們有必要對整個初始化過程有所認識,其中包括繼承,對這個過程中發生的事情有一個整體性的概念。
請觀察下述代碼:

//: Beetle.java
// The full process of initialization.

class Insect {
int i = 9;
int j;
Insect() {
    prt("i = " + i + ", j = " + j);
    j = 39;
}
static int x1 =
    prt("static Insect.x1 initialized");
static int prt(String s) {
    System.out.println(s);
    return 47;
}
}

public class Beetle extends Insect {
int k = prt("Beetle.k initialized");
Beetle() {
    prt("k = " + k);
    prt("j = " + j);
}
static int x2 =
    prt("static Beetle.x2 initialized");
static int prt(String s) {
    System.out.println(s);
    return 63;
}
public static void main(String[] args) {
    prt("Beetle constructor");
    Beetle b = new Beetle();
}
} ///:~

該程式的輸出如下:

static Insect.x initialized
static Beetle.x initialized
Beetle constructor
i = 9, j = 0
Beetle.k initialized
k = 63
j = 39

對Beetle運行Java時,發生的第一件事情是裝載程式到外面找到那個類。在裝載過程中,
裝載程式注意它有一個基礎類(即extends關鍵字要表達的意思),所以隨之將其載入。
無論是否準備產生那個基礎類的一個對象,這個過程都會發生(請試著將對象的建立代碼當作注釋標註出來,
自己去證實)。
若基礎類含有另一個基礎類,則另一個基礎類隨即也會載入,以此類推。接下來,
會在根基礎類(此時是Insect)執行static初始化,再在下一個衍生類執行,以此類推。
保證這個順序是非常關鍵的,因為衍生類的初始化可能要依賴於對基礎類成員的正確初始化。
此時,必要的類已全部裝載完畢,所以能夠建立對象。
首先,這個對象中的所有基礎資料型別 (Elementary Data Type)都會設成它們的預設值,而將物件控點設為null。
隨後會調用基礎類構建器。在這種情況下,調用是自動進行的。
但也完全可以用super來自行指定構建器調用(就象在Beetle()構建器中的第一個操作一樣)。
基礎類的構建採用與衍生類構建器完全相同的處理過程。基礎順構建器完成以後,
執行個體變數會按本來的順序得以初始化。最後,執行構建器剩餘的主體部分

--------------------------------------------------------------------------------
6.8 final關鍵字
由於語境(應用環境)不同,final關鍵字的含義可能會稍微產生一些差異。但它最一般的意思就是聲明
“這個東西不能改變”。之所以要禁止改變,可能是考慮到兩方面的因素:設計或效率。由於這兩個原因頗有些區別,
所以也許會造成final關鍵字的誤用。
在接下去的小節裡,我們將討論final關鍵字的三種應用場合:資料、方法以及類。

6.8.1 final資料
許多程式設計語言都有自己的辦法告訴編譯器某個資料是“常數”。常數主要應用於下述兩個方面:
(1) 編譯期常數,它永遠不會改變
(2) 在運行期初始化的一個值,我們不希望它發生變化
對於編譯期的常數,編譯器(程式)可將常數值“封裝”到需要的計算過程裡。也就是說,
計算可在編譯期間提前執行,從而節省運行時的一些開銷。在Java中,
這些形式的常數必須屬於基礎資料型別 (Elementary Data Type)(Primitives),而且要用final關鍵字進行表達。
在對這樣的一個常數進行定義的時候,必須給出一個值。
無論static還是final欄位,都只能儲存一個資料,而且不得改變。
若隨同物件控點使用final,而不是基礎資料型別 (Elementary Data Type),它的含義就稍微讓人有點兒迷糊了。
對於基礎資料型別 (Elementary Data Type),final會將值變成一個常數;但對於物件控點,final會將控制代碼變成一個常數
。進行聲明時,必須將控制代碼初始化到一個具體的對象。而且永遠不能將控制代碼變成指向另一個對象。
然而,對象本身是可以修改的。Java對此未提供任何手段,可將一個對象直接變成一個常數(但是,
我們可自己編寫一個類,使其中的對象具有“常數”效果)。這一限制也適用於數組,它也屬於對象。
2. 空白final
Java 1.1允許我們建立“空白final”,它們屬於一些特殊的欄位。儘管被聲明成final,
但卻未得到一個初始值。無論在哪種情況下,空白final都必須在實際使用前得到正確的初始化。
而且編譯器會主動保證這一規定得以貫徹。然而,對於final關鍵字的各種應用,空白final具有最大的靈活性。
舉個例子來說,位於類內部的一個final欄位現在對每個對象都可以有所不同,同時依然保持其“不變”的本質。
3. final自變數
Java 1.1允許我們將自變數設成final屬性,方法是在自變數列表中對它們進行適當的聲明。
這意味著在一個方法的內部,我們不能改變自變數控制代碼指向的東西。

注意此時仍然能為final自變數分配一個null(空)控制代碼,同時編譯器不會捕獲它。
這與我們對非final自變數採取的操作是一樣的。
我們只能讀取自變數,不可改變它。

6.8.2 final方法
之所以要使用final方法,可能是出於對兩方面理由的考慮。第一個是為方法“上鎖”,
防止任何繼承類改變它的本來含義。設計程式時,若希望一個方法的行為在繼承期間保持不變,
而且不可被覆蓋或改寫,就可以採取這種做法。
採用final方法的第二個理由是程式執行的效率。將一個方法設成final後,
編譯器就可以把對那個方法的所有調用都置入“嵌入”調用裡。只要編譯器發現一個final方法調用,
就會(根據它自己的判斷)忽略為執行方法調用機制而採取的常規代碼插入方法(將自變數壓入堆棧;
跳至方法代碼並執行它;跳回來;清除堆棧自變數;最後對傳回值進行處理)。相反,
它會用方法主體內實際代碼的一個副本來替換方法調用。這樣做可避免方法調用時的系統開銷。
當然,若方法體積太大,那麼程式也會變得雍腫,可能受到到不到內嵌程式碼所帶來的任何效能提升。
因為任何提升都被花在方法內部的時間抵消了。Java編譯器能自動偵測這些情況,並頗為“明智”
地決定是否嵌入一個final方法。然而,最好還是不要完全相信編譯器能正確地作出所有判斷。
通常,只有在方法的代碼量非常少,或者想明確禁止方法被覆蓋的時候,才應考慮將一個方法設為final。
類內所有private方法都自動成為final。由於我們不能訪問一個private方法,
所以它絕對不會被其他方法覆蓋(若強行這樣做,編譯器會給出錯誤提示)。
可為一個private方法添加final指示符,但卻不能為那個方法提供任何額外的含義。

6.8.3 final類
如果說整個類都是final(在它的定義前冠以final關鍵字),就表明自己不希望從這個類繼承,
或者不允許其他任何人採取這種操作。換言之,出於這樣或那樣的原因,我們的類肯定不需要進行任何改變;
或者出於安全方面的理由,我們不希望進行子類化(子類處理)。
除此以外,我們或許還考慮到執行效率的問題,並想確保涉及這個類各對象的所有行動都要儘可能地有效。

注意資料成員既可以是final,也可以不是,取決於我們具體選擇。應用於final的規則同樣適用於資料成員,
無論類是否被定義成final。將類定義成final後,結果只是禁止進行繼承——沒有更多的限制。然而,
由於它禁止了繼承,所以一個final類中的所有方法都預設為final。因為此時再也無法覆蓋它們。
所以與我們將一個方法明確聲明為final一樣,編譯器此時有相同的效率選擇。
可為final類內的一個方法添加final指示符,但這樣做沒有任何意義。

6.8.4 final的注意事項
設計一個類時,往往需要考慮是否將一個方法設為final。可能會覺得使用自己的類時執行效率非常重要,
沒有人想覆蓋自己的方法。這種想法在某些時候是正確的。
但要謹慎作出自己的假定。通常,我們很難預測一個類以後會以什麼樣的形式再生或重複利用。
常規用途的類尤其如此。若將一個方法定義成final,
就可能杜絕了在其他程式員的項目中對自己的類進行繼承的途徑,因為我們根本沒有想到它會象那樣使用。
標準Java庫是闡述這一觀點的最好例子。其中特別常用的一個類是Vector。如果我們考慮代碼的執行效率,
就會發現只有不把任何方法設為final,才能使其發揮更大的作用。
我們很容易就會想到自己應繼承和覆蓋如此有用的一個類,但它的設計者卻否定了我們的想法。
但我們至少可以用兩個理由來反駁他們。首先,Stack(堆棧)是從Vector繼承來的,
亦即Stack“是”一個Vector,這種說法是不確切的。其次,對於Vector許多重要的方法,
如addElement()以及elementAt()等,它們都變成了synchronized(同步的)。
正如在第14章要講到的那樣,這會造成顯著的效能開銷,可能會把final提供的效能改善抵銷得一乾二淨。
因此,程式員不得不猜測到底應該在哪裡進行最佳化。在標準庫裡居然採用了如此笨拙的設計,
真不敢想象會在程式員裡引發什麼樣的情緒。
另一個值得注意的是Hashtable(散列表),它是另一個重要的標準類。該類沒有採用任何final方法。
正如我們在本書其他地方提到的那樣,顯然一些類的設計人員與其他設計人員有著全然不同的素質
(注意比較Hashtable極短的方法名與Vecor的方法名)。對類庫的使用者來說,這顯然是不應該如此輕易就能看出的。
一個產品的設計變得不一致後,會加大使用者的工作量。
這也從另一個側面強調了代碼設計與檢查時需要很強的責任心。
--------------------------------------------------------------------------------------------------

7.6 內部類
--------------------------------------------------------------------
在Java 1.1中,可將一個類定義置入另一個類定義中。這就叫作“內部類”。
內部類對我們非常有用,因為利用它可對那些邏輯上相互聯絡的類進行分組,
並可控制一個類在另一個類裡的“可見度”。然而,
我們必須認識到內部類與以前講述的“合成”方法存在著根本的區別。
通常,對內部類的需要並不是特別明顯的,至少不會立即感覺到自己需要使用內部類。
在本章的末尾,介紹完內部類的所有文法之後,大家會發現一個特別的例子。
通過它應該可以清晰地認識到內部類的好處。
建立內部類的過程是平淡無奇的:將類定義置入一個用於封裝它的類內部

若想在除外部類非static方法內部之外的任何地方產生內部類的一個對象,
必須將那個對象的類型設為“外部類名.內部類名”,

7.6.1 內部類和上溯造型
迄今為止,內部類看起來仍然沒什麼特別的地方。
畢竟,用它實現隱藏顯得有些大題小做。
Java已經有一個非常優秀的隱藏機制——只允許類成為“友好的”(只在一個包內可見),
而不是把它建立成一個內部類。
然而,當我們準備上溯造型到一個基礎類(特別是到一個介面)的時候,
內部類就開始發揮其關鍵作用(從用於實現的對象產生一個介面控制代碼具有與上溯造型至一個基礎類相同的效果)。
這是由於內部類隨後可完全進入不可見或不可用狀態——對任何人都將如此。
所以我們可以非常方便地隱藏實施細節。我們得到的全部回報就是一個基礎類或者介面的控制代碼,
而且甚至有可能不知道準確的類型。

7.6.2 方法和範圍中的內部類
至此,我們已基本理解了內部類的典型用途。對那些涉及內部類的代碼,
通常表達的都是“單純”的內部類,非常簡單,且極易理解。
然而,內部類的設計非常全面,

不可避免地會遇到它們的其他大量用法——假若我們在一個方法甚至一個任意的範圍內建立內部類。
有兩方面的原因促使我們這樣做:
(1) 正如前面展示的那樣,我們準備實現某種形式的介面,使自己能建立和返回一個控制代碼。
(2) 要解決一個複雜的問題,並希望建立一個類,用來輔助自己的程式方案。同時不願意把它公開。

在下面這個例子裡,將修改前面的代碼,以便使用:
(1) 在一個方法內定義的類
(2) 在方法的一個範圍內定義的類
(3) 一個匿名類,用於實現一個介面
(4) 一個匿名類,用於擴充擁有非預設構建器的一個類
(5) 一個匿名類,用於執列欄位初始化
(6) 一個匿名類,通過執行個體初始化進行構建(匿名內部類不可擁有構建器)

7.6.4 static內部類
為正確理解static在應用於內部類時的含義,必須記住內部類的對象預設持有建立它的那個封裝類的一個對象的控制代碼。
然而,假如我們說一個內部類是static的,這種說法卻是不成立的。static內部類意味著:
(1) 為建立一個static內部類的對象,我們不需要一個外部類對象。
(2) 不能從static內部類的一個對象中訪問一個外部類對象。
但在存在一些限制:由於static成員只能位於一個類的外部層級,
所以內部類不可擁有static資料或static內部類。
倘若為了建立內部類的對象而不需要建立外部類的一個對象,那麼可將所有東西都設為static。
為了能正常工作,同時也必須將內部類設為static

7.7 構建器和多型
同往常一樣,構建器與其他種類的方法是有區別的。在涉及到多型的問題後,這種方法依然成立。
儘管構建器並不具有多型(即便可以使用一種“虛擬構建器”——將在第11章介紹),
但仍然非常有必要理解構建器如何在複雜的分級結構中以及隨同多型使用。
這一理解將有助於大家避免陷入一些令人不快的糾紛。

7.7.1 構建器的調用順序
構建器調用的順序已在第4章進行了簡要說明,但那是在繼承和多型問題引入之前說的話。
用於基礎類的構建器肯定在一個衍生類的構建器中調用,而且逐漸向上連結,
使每個基礎類使用的構建器都能得到調用。之所以要這樣做,是由於構建器負有一項特殊任務:
檢查對象是否得到了正確的構建。一個衍生類只能訪問它自己的成員,不能訪問基礎類的成員
(這些成員通常都具有private屬性)。
只有基礎類的構建器在初始化自己的元素時才知道正確的方法以及擁有適當的許可權。
所以,必須令所有構建器都得到調用,否則整個對象的構建就可能不正確。
那正是編譯器為什麼要強迫對衍生類的每個部分進行構建器調用的原因。
在衍生類的構建器主體中,若我們沒有明確指定對一個基礎類構建器的調用,
它就會“默默”地調用預設構建器。如果不存在預設構建器,
編譯器就會報告一個錯誤(若某個類沒有構建器,編譯器會自動組織一個預設構建器)。

對於一個複雜的對象,構建器的調用遵照下面的順序:
(1) 調用基礎類構建器。這個步驟會不斷重複下去,首先得到構建的是分級結構的根部,
然後是下一個衍生類,等等。直到抵達最深一層的衍生類。
(2) 按聲明順序調用成員初始化模組。
(3) 調用衍生構建器的主體。

構建器調用的順序是非常重要的。進行繼承時,我們知道關於基礎類的一切,
並且能訪問基礎類的任何public和protected成員。這意味著當我們在衍生類的時候,
必須能假定基礎類的所有成員都是有效。採用一種標準方法,構建行動已經進行,
所以對象所有部分的成員均已得到構建。但在構建器內部,必須保證使用的所有成員都已構建。
為達到這個要求,唯一的辦法就是首先調用基礎類構建器。然後在進入衍生類構建器以後,
我們在基礎類能夠訪問的所有成員都已得到初始化。此外,
所有成員對象(亦即通過合成方法置於類內的對象)在類內進行定義的時候(比如上例中的b,c和l),
由於我們應儘可能地對它們進行初始化,所以也應保證構建器內部的所有成員均為有效。
若堅持按這一規則行事,會有助於我們確定所有基礎類成員以及當前對象的成員對象均已獲得正確的初始化。
但不幸的是,這種做法並不適用於所有情況,這將在下一節具體說明。

前一節講述的初始化順序並不十分完整,而那是解決問題的關鍵所在。初始化的實際過程是這樣的:
(1) 在採取其他任何操作之前,為對象分配的儲存空間初始化成二進位零。
(2) 就象前面敘述的那樣,調用基礎類構建器。此時,被覆蓋的draw()方法會得到調用(的確是在RoundGlyph構建器調用之前),此時會發現radius的值為0,這是由於步驟(1)造成的。
(3) 按照原先聲明的順序調用成員初始化代碼。
(4) 調用衍生類構建器的主體。

採取這些操作要求有一個前提,那就是所有東西都至少要初始化成零

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.