1. 程式人生 > 程式設計 >【推薦】【Java程式設計思想】【筆記】

【推薦】【Java程式設計思想】【筆記】

工欲善其事必先利其器!

再快不能快基礎,再爛不能爛語言!

第一章 物件導論

"我們之所以將自然界分解,組織成各種概念,並按其含義分類,主要是因為我們是整個口語交流社會共同遵守的協定的參與者,這個協定以語言的形式固定下來......除非贊成這個協定中規定的有關語言資訊的組織和分類,否則我們根本無法交談。———Benjamin Lee Whorf( 1897~1941 )"

1.1 抽象過程

  • 面向物件的特性:
    1. 萬物皆物件。 將物件視為奇特的變數,它可以儲存資料,除此之外,你還可以要求它 在自身上執行操作。理論上講,你可以抽取待求解問題的任何概念化構建(狗,建築 物,服務等),將其表示為程式中的物件。
    2. 程式是物件的集合,他們通過傳送訊息來告知彼此所要做的。 要想請求一個物件,就 必須對該物件傳送一條訊息。更具體地說,可以把訊息想象為對某個特定物件的方法的 呼叫請求。
    3. 每個物件都有自己的由其他物件所構成的儲存。 換句話說,可以通過建立包含現有對 象的包的方式來建立新型別的物件。因此,可以在程式中構建複雜的體系,同時將其復 雜性影藏在物件的簡單性背後。
    4. 每個物件都擁有其型別。 按照通用的說法,“每個物件都是某個類(class)的一個實 例(instance)”,這裡“類”就是“型別”的同義詞。每個類最重要的區別於其他類的特性 就是“可以傳送什麼樣的訊息給它”。
    5. 某一特定型別的所有物件都可以接收同樣的訊息。
      這是一句意味深長的表述,你在稍 後便會看到。因為“圓形”型別的物件同樣也是“幾何形”型別的物件,所以一個“圓形”物件 必定能夠接受傳送給“幾何形”物件的訊息。這意味著可以編寫與“幾何形”互動並自動處理 所有與幾何形性質相關的事務的程式碼。這種可替代性(substitutability)是OOP中最強 有力的概念之一。
  • 每一個物件在記憶體中都有一個唯一的地址(每一個物件都可以唯一地與其他物件區分開來)。

1.2 每個物件都有一個介面

  • 建立抽象資料型別(類)是面向物件程式設計的基本概念之一。

    抽象資料的執行方式與內建(built-in)型別幾乎完全一致:你可以建立某一型別的變數(按 照面向物件的說法,稱其為物件或者例項),然後操作這些變數(稱其為傳送訊息或者請 求;傳送訊息,物件就知道要做什麼)。

  • 每一個物件都屬於定義了特性和行為的某個特定的類。

  • 因為類描述了具有相同特性(資料元素)和行為(功能)的物件集合,所以一個類實際上就是一個資料型別。

  • 介面確定了對某一特定物件所能發出的請求。

1.3 每個物件都提供服務

  • 高內聚低耦合

    內聚: 是從功能角度來度量模組內的聯絡,一個好的內聚模組應當恰好做一件事。它描述 的是模組內的功能聯絡;

    耦合: 是軟體結構中各模組之間相互連線的一種度量,耦合強弱取決於模組間介面的複雜 程度、進入或訪問一個模組的點以及通過介面的資料。

    高內聚低耦合,是軟體工程中的概念,是判斷設計好壞的標準,主要是面向物件的設 計,主要是看類的內聚性是否高,耦合度是否低。

    耦合性: 也稱塊間聯絡。指軟體系統結構中各模組間相互聯絡緊密程度的一種度量。模 塊之間聯絡越緊密,其耦合性就越強,模組的獨立性則越差。模組間耦合高低取決於模 塊間介面的複雜性、呼叫的方式及傳遞的資訊

    內聚性: 又稱塊內聯絡。指模組的功能強度的度量,即一個模組內部各個元素彼此結合 的緊密程度的度量。若一個模組內各元素(語名之間、程式段之間)聯絡的越緊密,則 它的內聚性就越高。

    所謂高內聚是指一個軟體模組是由相關性很強的程式碼組成,只負責一項任務,也就是常 說的單一責任原則。

    耦合: 一個軟體結構內不同模組之間互連程度的度量。

    對於低耦合,粗淺的理解是:一個完整的系統,模組與模組之間,儘可能的使其獨立存 在。 也就是說,讓每個模組,儘可能的獨立完成某個特定的子功能。模組與模組之間的 介面,儘量的少而簡單。如果某兩個模組間的關係比較複雜的話,最好首先考慮進一步 的模組劃分。這樣有利於修改和組合。

  • 將物件看作是服務提供者還有一個附帶的好處:它有助於提高物件的內聚性。高內聚是 軟體設計的基本質量要求之一:這意味著一個軟體構造(例如一個物件,當然它也有可能 是指一個方法或一個物件庫)的各個方面“組合”得很好。人們在設計物件時所面臨的一個 問題是,將過多的功能都塞在一個物件中。

  • 在良好的面向物件設計中,每個物件都可以很好地完成一項任務,但是它並不試圖做更多的事情。

  • 將物件作為服務提供者看待是一件偉大的簡化工具,這不僅在設計過程中非常有用,而 且當其他人試圖理解你的程式碼或重用某個物件時,如果他們看出了這個物件所能提供的 服務的價值,它會使調整物件以適應其設計的過程變得簡單得多。

1.4 被影藏的具體實現

  • 將程式設計師開發人員按照角色分為類建立者(那些建立行資料型別的程式設計師)和客戶端程式 員(那些在其應用中使用資料型別的類消費者)是大有裨益的。客戶端程式設計師的目標是收 集各種用來實現快速應用開發的類。類建立者的目標是構建類,這種類只向客戶端程式 員暴露必須的部分,而隱藏其他部分。
  • 訪問控制存在的原因
    • 第一個存在的原因就是讓客戶端程式設計師無法觸及他們不應該觸及的部分。
    • 第二個存在的原因就是允許庫設計者可以改變類內部的工作方式而不用擔心會影響到客戶端程式設計師。
  • JAVA邊界值
    • public: 表示緊隨其後的元素對任何人都是可用的。
    • private: 關鍵字表示除型別建立者和型別的內部方法以外的任何人都不能訪問的元素。 private就像是你與客戶端程式設計師之間的一堵磚牆,如果有人試圖訪問private成 就會在編譯時得到錯誤資訊。
    • protected: protected關鍵字與private作用相當,差別僅在於繼承的類可以訪問 protected成員,但是不能訪問private成員。
    • 預設訪問許可權: 當沒有使用前面提到的任何訪問指定詞時,它將發揮作用。這種許可權通常被稱為包訪問許可權,因為在這種許可權下,類可以訪問在同一個包(庫構件)中的其他類的成員,但是在包之外,這些成員如同指定了private一樣。

1.5 複用具體實現

  • 程式碼複用是面向物件程式設計語言鎖提供的最了不起的優點之一。

  • 新的型別可以由任意數量,任意型別的其他物件以任意可以實現新的類中想要的功能的方式組成。

  • 因為是在使用現有的類合成新的其他物件的類,所以這種概念被稱為組合(composition),如果組合是動態發生的,那麼它通常被稱為聚合(aggregation)。

  • 在建立新類時,應該首先考慮組合,因為它更加簡單靈活。 如果採用這種方式,設計會變得更加清晰,一旦有了一些經驗之後,便能夠看出必須使用繼承的場合了。

1.6 繼承

  • 當源類(被稱為基類,超類或父類)發生變動時,被修改的“副本”(被稱為匯出類,繼承類或者子類)也會反應出變動。

  • 可以建立一個基類來表示系統中某些物件的核心概念,從基型別中匯出其他型別,來表示此核心可以被實現的各種不同方式。

  • 當繼承現有型別時,也就建立了新的型別。 這個新的型別不僅包括現在型別的所有成員(儘管private成員被隱藏起來,並且不可訪問),而且更重要的是它複製了基類的介面。也就是說,所有可以傳送給基類物件的訊息同時也可以傳送到匯出類物件。

  • 通過繼承而產生的型別等價性是理解面向物件程式設計方法內涵的重要門檻!

  • 有兩種方法可以使基類與匯出類產生差異

      1. 直接在匯出類中新增新方法。這些新方法並不是基類介面的一部分。這意味著基類並不能滿足你的所有需求,因此必需新增更多的 方法。
      1. 使匯出類和基類之間產生差異的方法是改變現有基類的方法的行為,這被稱之為覆蓋(overriding)那個方法。
  • 繼承應該只覆蓋基類的方法(而不新增在基類中沒有的新方法),在某種意義上,這是一種處理繼承的理想方式。我們經常將這種情況下的基類與匯出類之間的關係稱為is-a(是一個)關係。

  • 有時必須在匯出型別新增新的介面元素,這樣也就擴充套件了介面。這種情況我們可以描述為is-like-a(像是一個)關係。

1.7 伴隨多型的可互換物件

  • 通過匯出新的子型別而輕鬆擴充套件設計的能力是對改動進行封裝的基本方式之一。

    這種能力可以極大地改善我們的設計,同時也降低軟體維護的代價。

  • 面向物件程式設計的最重要妙訣:編譯器不可能產生傳統意義上的函式呼叫。 一個非面向物件程式設計的編譯器產生的函式呼叫會引起所謂的前期繫結 ,這個術語你可能以前從未聽說過,可能從未想過函式呼叫的其他方式。這麼做意味著編譯器將產生對一個具體函式名字的呼叫,而執行時將這個呼叫解析到將要被執行的程式碼的絕對地址。然而在OOP(面向物件程式設計)中,程式直到執行時才能夠確定程式碼的地址,所以當訊息傳送到一個泛化物件時,必須採用其他的機制。

  • 當向物件傳送訊息時,被呼叫的程式碼直到執行時才能確定。 編譯器確保被呼叫方法的存在,並對呼叫引數和返回值執行型別檢查(無法提供此類保證的語言被稱為是弱型別的),但是並不知道將被執行的確切程式碼。

  • 在java中,動態繫結是預設行為,不需要新增額外的關鍵字來實現多型。

  • 把將被匯出類看做是它的基類的過程稱為向上轉型。

    轉型這個名稱的靈感來自於模型鑄造的塑模動作;而向上(up)這個詞來源於繼承圖的典型佈局方式;通常基類在頂部,而匯出類在其下部散開。因此,轉型為一個基類就是在繼承圖中向上移動,即“向上轉型”。

  • 正是因為多型才使得事情總是能夠被正確處理。編譯器和執行系統會處理相關的細節,你需要馬上知道的只是事情會發生,更重要的是怎樣通過它來設計。當向一個物件傳送訊息時,即使涉及向上轉型,該物件也知道要執行什麼樣的正確行為。

1.8 單根繼承結構

  • 在OOP中,是否所有的類最終都繼承自單一的基類?答案是yes,這個終極基類的名字就是Object。 事實證明,單根繼承結構帶來了很多好處。
  • 在單根繼承結構中的所有物件都具有一個共用的介面,所以它們歸根到底都是相同的基本型別。
  • 單根繼承結構保證所有物件都具備某些功能。
  • 單根繼承結構使垃圾回收器的實現變得容易很多,而垃圾回收器正是Java相對C++的重要改進之一。 由於所有物件都保證具有其型別資訊,因此不會因無法確定物件的型別而陷入僵局。這對於系統級操作(如異常處理)顯得尤其重要,並且給程式設計帶來了更大的靈活性。

1.9 容器(集合)

  • 容器(也稱為集合,不過java類庫以不同的含義使用“集合”這個術語,所以本書使用“容器”這個詞),在任何需要時都可擴充自己以容納你置於其中的所有東西。因此不需要知道將來會把多少個物件置於容器中,只需要建立一個容器物件,然後讓它處理所有細節。

  • Java容器

    • List(用於儲存序列)
    • Map(也被稱為關聯陣列,是用來建立物件之間的關聯)
    • Set(每種物件型別只持有一個)
    • 諸多佇列,樹,堆疊等更多的構件。
  • 使用時還是要對容器有所選擇,原因如下:

    • 不同容器提供了不同型別的介面和外部行為。堆疊相比於佇列就具備不同的介面和行為,也不同於集合列表的介面和行為。它們之中的某些容器提供的解決方案可能要比其他容器靈活得多。
    • 不同的容器對於某些操作具有不同的效率。最好的例子就是兩種List的比較:ArrayList和LinkedList。它們都是具有相同介面和外部行為的簡單的序列,但是它們對於某些操作所花費的代價卻有天壤之別。在ArrayList中,隨機訪問元素是一個花費固定時間的操作;但是,對於LinkedList來說,隨機選取元素需要在列表中移動,這種代價是高昂的,訪問越靠近表尾的元素,花費的時間越長。而另一方面,如果想在序列中間插入一個元素,LinkedList的開銷卻比ArrayList要小。
  • 我們可以在一開始使用LinkedList構建程式,而在優化系統效能時改用ArrayList。介面List所帶來的抽象,把在容器之間進行轉換時對程式碼產生的影響降低到最小限度。

  • 引數化型別(範型) 在JavaSE5之前,容器儲存的物件都只具有Java中的通用型別:Object。

    單根繼承結構意味著所有東西都是Object型別,所以可以儲存Object的容器可以儲存任何東西。這使得容器很容易被複用。

  • 向上轉型是安全的,例如Circle是一種Shape型別;但是不知道某個Object是Circle還是Shape,所以除非確切知道所要處理的物件的型別,否則向下轉型幾乎是不安全的。

  • 如果向下轉型錯誤的話,就會得到被稱為異常的執行時錯誤。

  • 引數化型別(範型)就是一個編譯器可以自動定製用於特定型別上的類。 例如,通過使用引數化型別,編譯器可以定製一個只接納和取出Shape物件的容器。

  • 為了利用泛型的優點,很多標準類庫構建都已經進行了修改。就像我們將要看到的那樣,範型對本書中的許多程式碼都產生了重要的影響。

1.10 物件的建立和生命期

  • 在使用物件時,最關鍵的問題之一便是它們的生成和銷燬方式。

    每個物件為了生存都需要資源,尤其是記憶體。當我們不再需要一個物件時,它必須被清理掉。使其佔有的資源可以被釋放和重用。

  • 為了追求最大的執行速度,物件的儲存空間和生命週期可以在編寫程式時確定。

    • 這可以通過將物件置於堆疊(它們有時被稱為自動變數)或限域變數或靜態儲存區域內來實現。

      這種方式將儲存空間分配和釋放置於優先考慮的位置,某些情況下這樣的控制非常有價值。但是,也犧牲了靈活性,因為必須在編寫程式時知道物件確切的數量,生命週期和型別。

    • 第二種方式是在被稱為堆(heap)的記憶體池中動態地建立物件。

      在這種方式中,直到執行時才知道需要多少物件,它們的生命週期如何,以及它們的具體型別是什麼。

  • 如果需要一個新物件,可以在需要的時刻直接在堆中建立。因為儲存空間是在執行時被動態管理的,所以需要大量的時間在堆中分配儲存空間,這可能要遠遠大於在堆疊中建立儲存空間的時間。

  • 建立堆疊儲存空間的時間依賴於儲存機制的設計

  • 動態方式有這樣一個一般性的邏輯假設:物件趨向於變得複雜。所以查詢和釋放儲存空間的開銷不會對物件的建立造成重大沖擊。動態方式所帶來的更大的靈活性正是解決一般化程式設計問題的要點所在。

  • java完全採用動態記憶體分配方式。每當想要建立新物件時,就要使用new關鍵字來構建此物件的動態例項。

  • 還有一個議題,就是生命週期。對於允許在堆疊上建立物件的語言,編譯器可以確定物件存活的時間,並可以自動銷燬它。

  • java提供了被稱為“垃圾回收器”的機制,它可以自動發現物件何時不再被使用,並繼而銷燬它。

    垃圾回收器非常有用,因為它減少了所必須考慮的議題和必須編寫的程式碼。更重要的是,垃圾回收器提供了更高層的保障,可以避免暗藏的記憶體洩漏問題。

  • java的垃圾回收器被設計用來處理記憶體釋放問題(儘管它不包括清理物件的其他方面)。垃圾回收器“知道”物件何時不再被使用,並自動釋放物件佔用的記憶體。

1.11 異常處理:處理錯誤

  • 異常是一種物件,它從出錯地點被“丟擲”,並被專門設計用來處理特定型別錯誤的相應的異常處理器“捕獲”。異常處理就像是與程式正常執行路徑並行的、在錯誤發生時執行的另一條路徑。因為它是另一條完全分離的執行路徑,所以它不會干擾正常的執行程式碼。
  • 被丟擲的異常不像方法返回的錯誤值和方法設定的用來表示錯誤條件的標誌位那樣可以被忽略,異常不能被忽略,所以它保證一定會在某處得到處理。
  • 異常提供了一種從錯誤狀況進行可靠恢復的途徑。現在不再是隻能退出程式,你可以經常進行校正,並恢復程式的執行,這些都有助於編寫出更健壯的程式。

1.12 併發程式設計

  • 在計算機程式設計中有一個基本的概念,就是在同一時刻處理多個任務的思想。
  • 在程式中,彼此獨立執行的部分稱之為執行緒。
  • 對於大量的問題,把問題切分成多個可獨立執行的部分(任務),從而提高程式的響應能力。稱之為“併發”。
  • 執行緒只是一種為單一處理器分配執行時間的手段。
  • 如果有多個並行任務都要訪問同一項資源,那麼就會出問題。例如,兩個程式不能同時向一臺列印機傳送資訊。為瞭解決這個問題,可以共享的資源,例如印表機,必須在使用期間被鎖定。因此,整個過程是:某個任務鎖定某項資源,完成其任務,然後釋放資源鎖,使其他任務可以使用這項資源。

第2章 一切都是物件

"如果我們說另一種不同的語言,那麼我們就會發覺一個有些不同的世界。——Luduing Wittgerstein( 1889~1951 )"

2.1 用引用操縱物件

  • java中,一切都被視為物件,因此可採用單一的語法。儘管一切都看作是物件,但操作的識別符號實際上是物件的一個“引用”。

    例:遙控器(引用)操作電視機(物件)。

    • java操作的識別符號實際上是物件的一個“引用”
    • java操作的識別符號實際上是物件的一個“引用”
    • java操作的識別符號實際上是物件的一個“引用”

2.2 必須由你建立所有物件

  • new 關鍵字的意思是“給我一個新物件”。
  • 儲存資料的地方:
    • 暫存器: 最快的儲存區,但由於暫存器的數量極其有限,所以根據需求進行分配。你不能直接控制,在程式中也感覺不到暫存器存在的任何跡象。

    • 堆疊: 位於通用RAM(隨機訪問儲存器)中。堆疊指標若往下移則分配新記憶體,若往上移動則釋放記憶體。

      堆疊:就是STACK。實際上是隻有一個出入口的佇列,即後進先出(First In Last Out),先分配的記憶體必定後釋放。一般由系統自動分配,存放函式的引數值,區域性變數等,自動清除。

      堆疊是每個函式進入的時候分一小塊,函式返回的時候就釋放了

      區域性變數放在堆疊中,所以函式返回,區域性變數就全沒了。

    • 堆: 一種通用的記憶體池(位於RAM區),用來存放所有的Java物件。

      當需要一個物件時,只需用new寫一行簡單的程式碼,當執行這行程式碼時,會自動在堆裡進行儲存分配。

      用堆進行儲存分配和清理可能比用堆疊進行儲存分配需要更多的時間。

    • 常量儲存: 常量值通常是直接存放在程式程式碼內部。

    • 非RAM儲存: 如果資料完全存活於程式之外,那麼它可以不受程式的任何控制,在程式沒有執行也可以存在。例如:流物件和持久化物件。

  • 基本資料型別
    • 基本資料型別不用new來建立物件,而是建立一個並非是引用的“自動”變數。這個變數直接儲存“值”,並置於堆疊中,因此更加高效。
    • 所有資料型別都有正負號,沒有無符號的數值型別。
    • 基本型別具有的包裝器類,使得可以在堆中建立一個非基本物件,用來表示對應的及本型別。
    • java SE5的自動包裝功能將自動地將基本資料型別轉換成包裝器型別。
    • java提供了兩個用於高精度計算的類:BigInteger(支援任意精度的整數)和BigDecimal(支援任何精度的定點數)。

2.3 永遠不需要銷燬物件

  • 作用域
    • 作用域由花括號的位置決定。
    • 作用域裡定義的變數只可用於作用域結束之前。
  • 物件的作用域
    • 當new建立一個java物件時,它可以存活於作用域之外。
    • java有一個垃圾回收器,用來監視用new建立的所有物件,並辨別那些不會再被引用的物件。隨後,釋放這些物件的記憶體空間,以便提供其他新的物件使用。

2.4 建立新的資料型別:類

  • class這個關鍵字之後緊跟的是新型別的名稱。
  • 在java中你所做的全部工作就是定義類,產生那些類的物件,以及傳送訊息給這些物件。
  • 欄位可以是任何型別,可以通過其引用與其進行通訊,也可以是基本型別的一種。
  • 如果欄位是對某個物件的引用,那麼必須初始化該引用,以便使其與一個實際的物件相關聯。

2.5 方法、引數和返回值

  • java的方法決定了一個物件能夠接收什麼樣的訊息,組成(名稱、引數、返回值、方法)。
  • 方法名和引數列表唯一地標識出某個方法。
  • 呼叫方法的行為通常被稱為傳送訊息給物件。
  • 面向物件的程式設計通常簡單地歸納為“向物件傳送訊息”。
  • 通常,儘管傳遞的是物件,而實際上傳遞的是物件的引用。

2.6 構建一個java程式

  • java通過域名來保證每一個類都是獨一無二的。
  • static關鍵字
    • 執行new來建立物件時,資料儲存空間才被分配,其方法才供外界呼叫。
    • new建立物件滿足不了的兩種場景:
        1. 只想為某特定域分配單一儲存空間,而不去考慮究竟要建立多少物件,甚至根本就不建立任何物件。
        1. 希望某個方法不與包含它的類的任何物件關聯在一起。也就是說沒有建立物件,也能夠呼叫這個方法。
      • 【解決】當宣告一個事物是static時,就意味著這個域或方法不會與包含它的類的任何物件例項關聯在一起。
    • 使用static方法(或域)前不需要建立任何物件。
    • 一個static欄位對每個類來說只有一份儲存空間,而非static欄位則是對每個物件都有一個儲存空間

第3章 操作符

3.7 關係操作符

  • new出來的兩個Integer,儘管物件內容相同,但物件的引用卻是不同的,== 和 != 比較的就是物件的引用。
  • 如果想比較兩個物件實際內容是否相同,必須使用所有物件都適用的equals()
  • 基本型別直接使用== 和 !=

第4章 控制執行流程

  • java不允許我們將一個數字作為布林值使用!
  • return關鍵字有兩方面的用途:
    • 一方面指定一個方法返回什麼值
    • 另一方面它會導致當前的方法退出,並返回那個值。
  • break用於強行退出迴圈,不執行迴圈中剩餘的語句。
  • 而continue則停止執行當前的迭代,然後退回迴圈起止處,開始下一次迭代。

第5章 初始化與清理

5.1 用構造器確保初始化

  • 構造器採用與類相同的名稱,在建立物件時,new XXX()將會為物件分配儲存空間,並呼叫相應的構造器。這就確保了在操作物件之前,它已經初始化了。
  • 每一類都會有一個預設構造器,java檔案中稱為“無參構造器”。
  • 構造器是一種特殊型別的方法,沒有返回值。
  • 在java中,“初始化”和“建立”捆綁在一起,兩者不能分離!

5.2 方法過載

  • 過載:方法名相同而形式引數不同。
  • 每個過載的方法都必須有一個獨一無二的引數型別列表。
  • 不可以根據返回值來區分過載方法。
  • 過載最常見的應用場景就是構造器過載!

5.3 預設構造器

  • 預設構造器(又名“無參”構造器)是沒有形式引數的——它的作用是建立一個“預設物件”。
  • 假如類中沒有構造器的話,編譯器會自動幫你建立一個預設構造器。但假如你已寫了一個構造器(有參構造),則會自動覆蓋掉預設構造器(也就是說此時類裡面沒有預設的構造器(無參構造),如果你還需要無參構造這時你要自己手動新增一個無參構造)。

5.4 this關鍵字

class Banana { void peel(int i) {/*...*/} }

public class BananaPeel{
    public static void main(String[] args) {
        Banana a = new Banana();
        Banana b = new Banana();
        a.peel(1);
        b.peel(2);
    }
}
複製程式碼

【問題】peel()如何知道是被a還是被b呼叫的呢?

【答】:為了能簡便面向物件的語法來編寫程式碼,編譯器暗自把“操作物件的引用”作為第一個引數傳遞給peel(),
       a.peel(a,1);這是內部表現形式。
複製程式碼
  • 如果在方法內部的時候需要獲得當前物件的引用,可以使用this關鍵字!
  • this關鍵字只能在方法的內部呼叫!
  • 通常寫 this 的時候,都是指“這個物件”或者“當前物件”,而且它本身表示對當前物件的引用。
  • 可以用this呼叫一個構造器,但卻不能呼叫兩個,而且必須將構造器置於最起始處,否則會報錯。
  • 除構造器以外,編譯器禁止在其他任何方法中呼叫構造器!
  • static 就是沒有this的方法
  • static方法的內部不能呼叫非靜態方法
  • static可以在沒有建立物件的前提下,僅僅通過類本身來呼叫static方法。

5.5 清理:終結處理與垃圾回收

  • java中的垃圾回收器負責回收無用物件佔據的記憶體資源。

  • 垃圾回收器只知道釋放由new分配的記憶體。

  • finalize():在垃圾回收器準備好釋放回收物件佔用的儲存空間之前呼叫的方法,可以自定義做一些清理工作。

    【問題】finalize()和解構函式的區別:

      解構函式(C++中銷燬物件必須用這個函式):“析構”函式與建構函式相反,當物件結束其生命週期,
      如物件所在的函式已呼叫完畢時,系統自動執行解構函式。解構函式往往用來做“清理善後”的工作。
    複製程式碼
  • Java物件並非總是被垃圾回收:

    1. 物件可能不被垃圾回收。
    2. 垃圾回收並不等於“析構”。
    3. 垃圾回收只與記憶體有關。
  • 在java中,只要程式沒有瀕臨儲存用完的那一刻,物件佔用的空間就總也得不到釋放。

  • 使用垃圾回收器的唯一原因是為了回收程式不再使用的記憶體。

  • finalize()的用途之——釋放空間:

    1. 無論物件是如何建立的,垃圾回收器都會負責釋放物件佔據的所有記憶體。

    當程式碼中使用“本地方法”的情況下,比如呼叫C的malloc()函式系列來分配儲存空間時,需要在finalize中呼叫free()來釋放記憶體空間。

    注:本地方法是一種在java中呼叫非java程式碼,一般用“native”關鍵字修飾。

  • finalize()的用途之——終結條件驗證:

    在物件資源被釋放之前驗證物件的某個狀態,將驗證內容放在finalize()的方法中,可以避免一些由於程式碼書寫問題導致的缺陷。

    注:【Effective Java】中“避免使用終結方法和清楚方法”一節中提到:終結方法(finalize)通常是不可預測的,也是很危險的,一般情況下是不必要的。使用終結方法會導致行為不穩定,效能降低,以及可移植性問題,根據經驗,應該避免使用終結方法。

  • 垃圾回收器如何工作

    在堆上分配物件代價是十分高昂的,因此java中所有物件的分配方式也是非常高昂的。然而垃圾回收器對於提高物件的建立速度,卻具有明顯的效果。

    • 在某些java虛擬機器器中,堆的實現就相當與一個傳送帶,每分配一個物件,它就往前移動一格,這意味著物件儲存的分配速度非常快。
    • java的堆並未完全像傳送帶那樣工作。
    • 當它工作時,一面回收空間,一面使堆中的物件緊湊排列,這樣“堆指標”就可以很容易移動到更靠近傳送帶的開始處,也就避免來頁面錯誤。通過垃圾回收器對物件重寫排列,實現了一種高速的,有無限空間可供分配的堆模型。
  • 垃圾回收器的實現:

    垃圾回收器依據的思想是:對任何“活”的物件,一定能最終追溯到其存在在堆疊或靜態儲存區之中的引用。

    • 引用計數器: 是一種簡單但速度很慢的垃圾回收技術。

      每個物件都含有一個引用計數器,當有引用連線至物件時,引用計數加1。當引用離開作用域或設定為null的時候,引用計數器減1。垃圾回收器會在含有全部物件的列表上便利,當發現某個物件的引用計數為0時,就釋放其佔用的空間。但迴圈引用會導致“物件應該被回收,計數卻不為0”。

      引用計數未被應用於任何一種java虛擬機器器實現中。

    • 停止,複製:

      先暫停程式的執行(不屬於後臺回收模式),然後將所有存活的物件從當前堆複製到另一個堆,沒有被複制的全部都是垃圾。當把物件從一處搬到另一處時,所有指向它的那些引用都必須修正了。位於堆或靜態儲存的引用可以直接被修改,但還有其他指向的引用,在遍歷的過程才能被找到。

      這種方式,效率會很低。一方面需要在兩個分離的堆之間來回倒騰;另一方面是程式穩定後,可能只會產生少量垃圾,甚至沒有垃圾,但仍然會將所有的記憶體自一處複製到另一處。

      垃圾回收動作不是在後臺進行的,垃圾回收動作發生時,程式會被暫停。

      “停止-複製”要求在釋放舊的物件之前,必須把所有存活的物件從舊堆裡複製到新堆。這將導致大量記憶體複製行為。

    • 標記,清掃

      從堆疊和靜態儲存區出發,遍歷所有的引用,進而找出所有存活的物件。每當找到一個存活物件,就會給物件設一個標記,但這時不回收任何物件。只有全部標記工作完成,才開始清理。沒有標記的將被釋放,有標記才會進行復制動作。必須在程式暫停的情況下才能進行。

    • “自適應的,分代的,停止-複製,標記-清掃”式垃圾回收器

      • 記憶體分配以較大的“塊”為單位,如果物件大,它會佔用獨立的塊。使用塊以後,垃圾回收器在回收的時候就可以往廢棄的塊裡拷貝物件了。
      • 每個塊都有相應的 代數(generation count) 來記錄它是否還存活。
      • 如果塊在某處被引用,其代數會增加,垃圾回收器將對上次回收動作之後新分配的塊進行整理(對於處理大量短命的臨時物件很有益處)。
      • 垃圾回收器會定期進行完整的清理動作——大型物件仍然不會被複制(只是其代數會增加),內含小型物件的那些快則被複制並整理。
      • java虛擬機器器會進行監視,如果所有物件都很穩定,垃圾回收器效率降低的話,就切換到 “標記-清掃” 方式;
      • java虛擬機器器會跟蹤“標記-清掃”的效果,要是堆空間出現很多碎片,就會切換到 “停止-複製” 方式; 根據不同的情況切換不同的方式,稱為“自適應”技術。

5.7 構造器初始化

  • 類中的變數初始化在構造器之前

  • 無論建立多少個物件,靜態資料都只佔一份儲存區域。

  • static關鍵不能應用於區域性變數,只能作用於域。

  • 如果是一個靜態的基本型別域,且沒有初始化,那麼它就會獲得基本型別的標準初值。

    如果它是一個物件引用,那麼它的預設初始值就是null。

  • 靜態儲存初始化時間:

    類中的靜態成員都會隨著類的載入而載入,比如建立物件時,或者是被引用時!

    類中的靜態成員都會隨著類的載入而載入,比如建立物件時,或者是被引用時!

    類中的靜態成員都會隨著類的載入而載入,比如建立物件時,或者是被引用時!

  • 初始化的順序是先靜態物件,而後“非靜態”物件。

  • 物件的建立過程

    1. 若沒有顯示地使用static方法,構造器實際上也是static方法,當物件被建立或者類的靜態方法/靜態域被首次訪問時,java直譯器要找到類路徑,然後定位.class檔案。
    2. 載入.class檔案,有關靜態初始化的所有動作都會執行。因此,靜態初始檔案只在Class物件首次載入的時候進行一次。
    3. 當使用new()建立的時候,將在堆上為物件分配足夠的空間。
    4. 這塊儲存空間會被清零,將所有基本資料都設定為預設值,引用被設定為null。
    5. 執行所有出現於欄位定義處的初始化動作。
    6. 執行構造器。

5.10 總結

  • 構造器能保證正確的初始化和清理(沒有正確的構造器呼叫,編譯器就不允許建立物件),所以有了完全的控制,也很安全。
  • 垃圾回收器會自動為物件釋放記憶體,所以在很多場合下,類似的清理方法在java中就不太需要了。
  • java的垃圾回收器可以極大地簡化程式設計工作,而且在處理記憶體的時候也更安全。

【推薦篇】- 書籍內容整理筆記 連結地址
【推薦】【Java程式設計思想】【筆記】 juejin.im/post/5dbb7a…
【推薦】【Java核心技術 卷Ⅰ】【筆記】 juejin.im/post/5dbb7b…

後期持續更新中。。。。。。

若有書寫錯誤的地方,歡迎留言,希望我們可以一起進步,一起加油!??