Java編程思想 學習筆記5

來源:互聯網
上載者:User

標籤:讀取   who   資料類型   之間   過程   最佳化   回收   checked   硬碟   

五、初始化與清理

 

1.用構造器確保初始化   

  在Java中,通過提供構造器,類的設計者可確保每個對象都會得到初始化。建立對象時,如果其類具有構造器,Java就會在使用者有能力操作對象之前自動調用相應的構造器,從而保證了初始化的進行。構造器的名稱與類的名稱相同。(“每個方法首字母小寫”的編碼風格並不適用於構造器)

  構造器有助於減少錯誤,並使代碼更易於閱讀。從概念上講,“初始化”與“建立”是彼此和獨立的。在Java中,“初始化”和“建立”捆綁在一起,兩者不能分離。
  構造器是一種特殊類型的方法,因為它沒有傳回值。這與傳回值為空白(void)明顯不同。構造器不會返回任何東西(new 運算式確實返回了對建立對象的引用,但構造器本身並沒有任何傳回值)。

2.方法重載   

  在Java裡,構造器是強制重載方法名的一個原因。既然構造器的名字已經由類名所決定,就只能有一個構造器名。那麼要想用多種方式建立一個對象該怎麼辦?假設你要建立一個類,即可以用標準方式進行初始化,也可以從檔案裡讀取資訊來初始化。這就需要兩個構造器:一個預設構造器,另一個取字串作為形式參數——該字串表示初始化對象所需的檔案名稱。由雩都是構造器,所它們必須有相同的名字,即類名。為了讓方法名相同而形式參數不同的構造器同時存在,必須用到方法重載

  ①區分重載方法

  每個重載方法都必須有一個獨一無二的參數類型列表。甚至參數順序的不同也足以區分兩個方法。不過,不建議這麼做。

  ②涉及基本類型的重載

  基本類型能從一個“較小”的類型自動提升至一個“較大”的類型,此過程一旦牽涉到重載,可能會造成了一些混淆。

  常數值會被當作int值處理。

  如果傳入的資料類型(實際資料類型)小於方法中聲明的形式參數類型,資料類型就會被提升。char類型略有不同,如果無法找到恰好接受char參數的方法,就會把char直接提升至int型。

  如果傳入的實際參數大於重載方法聲明的形式參數,就得通過類型轉換來執行窄化轉換。如果不這樣做,編譯器就會出錯。

  ③以傳回值區分重載方法

  在區分重載方法時,為什麼只能以類名和方法的形參列表作為標準呢?能否用方法的傳回值來區分呢?

  答案是不能

  因為有時,我們並不關心方法的傳回值,我們想要的是方法調用的其他效果,這時我們會調用方法而忽略傳回值。例如這樣調用方法: f();  

3.預設構造器

  預設構造器(又名“無參”構造器)是沒有形式參數的——它的作用是建立一個“預設對象”。如果你寫的類中沒有構造器,則編譯器會自動幫你建立一個預設構造器。但是,如果已經定義了一個構造器(無論是否有參數),編譯器就不會自動建立預設構造器。

4.this關鍵字 

  如果有同一類型的兩個對象,分別是a和b。你可能想知道,如何才能讓這兩個對象都能調用同一個方法呢:

class Banana { void peel(int i) { /*.....*/ } }public class BananaPeel {    public static void main(String[] args){        Banana a = new Banana(),                    b = new Banana();        a.peel(1);        b.peel(2);    }}

  如果只有一個peel方法,它如何知道是被a調用還是被b調用呢?

  為了能用簡便、物件導向的文法來編寫代碼——即“發送訊息給對象”,編譯器做了一些幕後工作。它暗自把“所操作的對象的引用”作為第一個參數傳遞給peel()。所以上述兩個方法的調用就變成了這樣:

  Banana.peel(a,1);

  Banana.peel(b,2);

  這時內部的表示形式。我們並不能這樣書寫代碼。

  假設你希望在方法的內部獲得對當前對象的引用。由於這個引用是由編譯器“偷偷”傳入的,所以沒有標識符可用。但是,為此有個專門的關鍵字:thisthis關鍵字只能在方法內部使用,表示對“調用方法的那個對象”的引用。this的用法和其他對象引用並無不同。但要注意,如果在方法內部調用用一個類的另一個方法,就不必使用this,直接調用即可。

  只在必要處使用this。

  ①在構造器中使用構造器

  可能為了一個類寫了多個構造器,有時可能想在一個構造器中調用另一個構造器,以避免重複代碼。可用this關鍵字做到這一點。

  通常寫this的時候,都是指“這個對象”或“當前對象”,而且它本身表示對當前對象的引用。在構造器中,如果為this添加了參數列表,那麼就有了不同的含義。這將產生對符合此參數列表的某個構造器的明確調用。另外,必須把構造器調用置於最起始處,否則編譯器會報錯。

  ②static的含義

  static方法就是沒有this的方法。在static方法的內部不能調用非靜態方法,反過來倒是可以的。(靜態方法可以建立自身的引用,和this效果一樣,通過這個引用可調用非靜態方法,例如main方法。)而且可以在沒有建立任何對象的前提下,僅僅通過類本身來調用static方法。

5.清理:終結處理和記憶體回收

  Java有記憶體回收行程負責回收無用對象佔據的記憶體資源。但也有特殊情況:假定你的對象(並非使用new)獲得了一塊“特殊”的記憶體地區,由於記憶體回收行程只知道釋放那些經由new分配的記憶體,所以它不知道該如何釋放該對象的這塊“特殊”記憶體。所以,Java允許在類中定義一個名為finalize()的方法。它的工作原理“假定”是這樣的:一旦記憶體回收行程準備好釋放對象佔用的儲存空間,將首先調用finalize()方法,並且在下一次記憶體回收動作發生時,才真正回收對象佔用的記憶體。

  這裡有一個潛在的編程陷阱,因為有些程式員(特別是C++程式員)剛開始可能會誤把finalize()當作C++中的折構函數(C++中銷毀對象必須用到這個函數)。所以有必要明確區分一下:在C++中,對象一定會被銷毀;而Java裡的對象卻並非總是被記憶體回收。或者換句話說:

  1.對象可能不被記憶體回收。

  2.記憶體回收不等於“折構”。

  這意味著在你不再需要某個對象之前,如果必須執行某些動作,那麼你得自己去做。Java並未提供“折構函數”或相似的概念,要做類似的清理工作,必須自己動手建立一個執行清理工作的普通方法。例如,假設某個對象在建立過程中會將自己繪製到螢幕上,如果不是明確地從螢幕上擦除,它可能永遠都得不到清理。如果在finalize()裡加入某種擦除功能,當“記憶體回收”發生時(不能保證一定發生),finalize()得到了調用,映像就會被擦除。要是“記憶體回收”沒有發生,映像就會一直保留下來。

·  也許你會發現,只要程式沒有瀕臨儲存空間用完的那一刻,對象佔用的空間就不會被釋放。如果程式執行結束,並且記憶體回收行程一直都沒有釋放你建立的任何對象的儲存空間,則隨著程式的退出,那些資源也會全部交還給作業系統。這個策略是恰當的,因為記憶體回收本身也有開銷,要是不使用它,那就不用支付這部分的開銷了。

  ①finalize()的用途何在

  finalize()的真正用途是什麼呢?

  這就引出需要記住的第三點:

  3.記憶體回收只與記憶體有關。

  也就是說,使用記憶體回收行程的唯一原因是為了回收程式不用的記憶體。無論對象是怎樣建立的,記憶體回收行程都會負責釋放對象佔據的所以記憶體。這就將對finalize()的需求限制到一種特殊情況,即通過某種建立對象方式以外的方式為對象分配了儲存空間。但是,Java中一切皆為對象,這種特殊情況又是怎麼回事呢?

 之所以要有finalize(),是由於在分配記憶體時可能採用了類似C語言中的做法。這種情況主要發生在使用“本地方法”的情況下,本地方法是一種在Java中調用非Java代碼的方式。本地方法目前只支援C和C++,但它們可以調用其他語言的代碼,所以實際上可以調用任何代碼。在非Java代碼中,可能會調用C的malloc()函數系列來分配儲存空間,而且除非調用了free()函數,否則儲存空間將得不到釋放,從而造成記憶體流失。當然,free()是C和C++中的函數,所以需要在finalize()中用本地方法調用它。

  ②你必須實施清理

  要清理一個對象,使用者必須在需要清理的時刻調用執行清理動作的方法。

  Java不允許建立局部對象,必須使用new建立對象。記住,無論是記憶體回收還是終結,都不保證一定發生。如果Java虛擬機器並未面臨記憶體耗盡的情形,它是不會浪費時間去執行記憶體回收的。

  ③終結條件

  通常,不能指望finalize(),必須建立其他的“清理”方法,並且明確地調用它們。看來,finalize()只能存在於程式員很難用到的一些晦澀用法裡了。不過,finalize()還有一個有趣的用法,它並不依賴每次都要對finalize()進行調用,這就是對象終結條件的驗證。

  當對某個對象不再感興趣——也就是它可以被清理了,這個對象應該處於某種狀態,使它佔用的記憶體可以被安全的釋放。例如,要是對象代表了一個開啟的檔案,在對象被回收前程式員應該關閉這個檔案。只要對象中存在沒有被適當清理的部分,程式就存在很隱晦的缺陷finalize()可以用來最終發現這種情況——儘管它並不總是被調用。如果某次finalize()的動作使得缺陷被發現,那麼就可據此找出問題所在——這才是人們真正關心的。

  以下是個簡單的例子:

class Book {    boolean checkedOut  = false;    Book(boolean checkedOut) {        this.checkedOut = checkedOut;     }    void checkIn() {        checkedOut = false;    }    protected void finalize() {        if(checkedOut) {            System.out.println("Error: checked out");        // 一般需要這麼做,假設基類版本的finalize()也要做某些事情       //由於需要異常處理,這裡省略//    super.fianlize();        }}public class TerminationCondition {    public static void main(String[] args) {        Book novel = new Book(true);        novel.checkIn();        new Book(true);        Sysytem.gc();    }}

 

  本例的終結條件是:所有的Book對象在被當作記憶體回收前都應該被簽入(check in)。但在main()方法中,由於程式員的錯誤,有一本書未被簽入。要是沒有finalize()來驗證終結條件,將很難發現這種缺陷。

  注意,System.gc()用於強制進行終結動作。即使不這麼做,通過重複地執行程式,最終也能找出錯誤的Book對象。

  ④記憶體回收行程如何工作

  Java從堆分配空間的速度,可以和其他語言從堆棧上分配空間的速度相媲美。

  打個比方,你可以把C++裡的堆想象成一個院子,裡面每個對象都負責管理自己的地盤。一段時間後,對象可能被銷毀,但地盤必須加以重用。在某些Java虛擬機器中,堆的實現截然不同:它更像一個傳送帶,每分配一個新對象,它就往前移動一格。這意味著Object Storage Service空間的分配速度非常快。Java的“堆積指標”只是簡單地移動到尚未分配的地區,其效率比得上C++在堆棧上分配空間的效率。

  事實上,Java中的堆未必完全像傳送帶那樣工作。要真是那樣的話,勢必會導致頻繁的記憶體頁面調度——將其移出移進硬碟,因此會顯得需要擁有比實際需要更多的記憶體。頁面調度會顯著地影響效能,最終,在建立了足夠多的對象之後,記憶體資源將耗盡。其中的秘密在於記憶體回收行程的介入。當它工作時,將一邊回收空間,一邊使堆中的對象緊湊排列,這樣“堆積指標”就可以很容易移動到更靠近傳送帶的開始處,也就盡量避免了分頁錯誤。通過記憶體回收行程對對象的重新排列,實現了一種高速的、有無限空間可供分配的堆模型。

  “自適應的、分代的、停止-複製、標記-清掃”式記憶體回收行程。

  Java虛擬機器中有許多附加技術用以提升速度。尤其是與載入器操作有關的,被稱為“即時”編譯器的技術。這種技術可以把程式全部或部分翻譯成本機機器碼,程式運行速度得以提升。當需要裝載某個類(通常是在為該類建立第一個對象)時,編譯器會先找到其.class檔案,然後將該類的位元組碼裝入記憶體。此時,有兩種方案可供選擇。一種是就讓即時編譯器編譯所有代碼,另一種則為即時編譯器只在需要時才編譯代碼,被稱為惰性評估。新版JDK中的Java HotSpot技術採用了類似方法,代碼每次被執行的時候都會做一些最佳化,所以執行的次數越多,它的速度越快。

6.成員初始化 

  Java儘力保證:所有變數在使用前都能得到恰當的初始話化。對於方法的局部變數,Java以編譯時間錯誤的形式來貫徹這種保證。

7.構造器初始化 

  可以用構造器來進行初始化。但要牢記:無法阻止自動初始化的進行,它將在構造器被調用之前發生。

  ①初始化的順序 

  在類的內部,變數定義的先後順序決定了初始化的順序。即使變數定義散佈於方法定義之間,它們仍舊會在任何方法(包括構造器)被調用之前得到初始化。

  ②待用資料的初始化

  無論建立多少個對象,待用資料都只佔用一份儲存地區。static關鍵字不能應用於局部變數,因此它只能作用於域。

  靜態初始化只在必要時才會進行。只有在第一次建立對象(或者第一次訪問待用資料)的時候,它們才會被初始化。此後,靜態對象不會再次被初始化。

  初始化的順序是先靜態對象,而後是“非靜態”對象。

  總結一下對象的建立過程,假設有個名為Dog的類:

  1.即使沒有顯式地使用static關鍵字,構造器實際上也是靜態方法。因此,當首次建立類型為Dog的對象時(構造器可以看成靜態方法),或者Dog類的靜態方法/靜態域首次被訪問時,Java解譯器必須尋找類路徑,以定位Dog.class檔案。

  2.然後載入Dog.class(這會建立一個Class對象),有關靜態初始化的所有動作都會執行。因此,靜態初始化只在Class對象首次載入的時候進行一次。

  3.當用new Dog()建立對象時,首先在堆上為Dog對象分配足夠的儲存空間。

  4.這Block Storage空間會被清零,這就自動地將Dog對象中的所有基本類型資料都設定成了預設值,而引用則設定成null。

  5.執行所有出現於欄位定義處的初始化動作。

  6.執行構造器。

  ③顯式的靜態初始化

  Java允許將多個靜態初始化動作組織成一個特殊的“靜態子句”(有時叫做“靜態塊”):

public class fool {    static int i;    static {        i = 666;    }}

  與其他靜態初始化動作一樣,這段代碼僅執行一次:當首次產生這個類的一個對象時,或者首次訪問屬於那個類的待用資料成員時(即使從未產生過那個類的對象)。

  ④非靜態執行個體初始化

  Java中也有被稱為執行個體初始化的類似文法,用來初始化每一個對象的非靜態變數。

  它與靜態初始化子句一模一樣,只不過少了static關鍵字。這種文法對於支援“匿名內部類”的初始化是必須的。

  執行個體初始化子句是在構造器之前執行的。

8.數組初始化 

  數組只是相同類型的,用一個標識符名稱封裝到一起的一個對象序列或基本類型資料序列。數組是通過方括弧下標操作符[ ]來定義和使用的。

  編譯器不允許指定數組的大小。這就又把我們帶回有關“引用”的問題上。現在擁有的只是對數組的一個引用(你已經為該引用分配了足夠的儲存空間),而且也沒給數組對象本身分配任何空間。可以用由一對花括弧括起來的值執行數組的初始化。

  也可以用花括弧括起來的列表來初始化對象數組。

  ①可變參數列表

  可變參數列表表現形式如下:

  void f(int... a){ }

  這樣就相當於有個int[] 的參數。有了可變參數列表,就再也不用顯式地編寫數組文法了,當你指定參數時,編譯器實際上會為你去填充數組。你擷取的仍舊是一個數組。

  注意:你應該總是只在重載方法的一個版本上使用可變參數列表,或者壓根就不是用它。

9.枚舉類型

  在Java SE5中添加了enum關鍵字,它使得我們在需要群組並使用枚舉類型時,可以很方便地處理。下面是一個簡單的例子:

public enum Spiciness {    NOT, MILD, MEDIUM, HOT, FAMING}

 

  這裡建立了一個名為Spiciness的枚舉類型,它具有5個具名值。由於枚舉類型的執行個體是常量,因此按照命名習慣它們都用大寫字母表示。

  為了使用enum,需要建立一個該類型的引用,並將其賦值給某個執行個體:

public class SimpleEnumUse {    public static void main(String[] args) {        Spiciness howHot = Spiciness.MEDIUM;        Sysytem.out.println(howHot);    }}/*outputMEDIUM*/

 

  枚舉類型可以使用ordinal()方法,用於表示某個特定enum常量的聲明順序,以及static value()方法,用來按照enum常量的聲明順序,產生這些常量值構成的數組。

  儘管enum看起來像是一種新的資料類型,但是這個關鍵字只是為enum產生對應的類時,產生了某些編譯器行為,因此在很大程度上,你可以將enum當作其他任何類來處理。事實上,enum確即時類,並且具有自己的方法。

  enum可以在switch語句內使用。

10.總結 

  初始化在Java中佔有至關重要的低位。

  學好記憶體分析對深刻理解Java運行機制十分重要,本文總結的仍舊很不全面,只是取了一部分,任重而道遠啊!

 

 

  

  

Java編程思想 學習筆記5

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.