Java編程思想第五版精粹(五)-初始化和清理(上)


1.1

初始化


比如C語言,我寫了整整半年,很多代碼bug是因為程序員忘記初始化導致的,比如指針.

對於更高級的語言,現實中的很多調包俠不知道怎麼才能初始化三方庫包裡的組件,甚至當俠客們必須得初始化這些三方組件時(而很多精簡的掉包俠根本不會管初始化問題)


1.2

清理

當使用完一個元素後,因為再也用不到了嘛,就很容易忘了它。哦豁,那個元素很容易忘記清理它。這樣就造成了元素使用的資源滯留不會被回收,直到程序消耗完所有的資源(特別是內存)。


2 構造器確保初始化

為解決問題 1.1,所以Java提供了構造器機制。類的設計者通過構造器保證每個對象的初始化。

那麼問題隨之而來了


2.1


怎麼命名構造器


存在兩個問題:

  1. 任何命名都可能與類中其他已有元素的名稱衝突
  2. 調用構造器是編譯器的職責,它必須知道該調用哪個方法

C++ 的解決方案看起來是最簡單且最符合邏輯的,所以 Java 使用了同樣的方式:構造器名稱與類名相同。冥冥之中就意味著在初始化過程中自動調用構造器。


2.2


怎麼使用構造器


當創建一個對象時:

<code>new MyObj()/<code>

分配存儲空間,調用構造器。構造器保證了對象在被使用前執行了正確的初始化。


構造器方法名與類名相同,不需要符合首字母小寫的編程風格


在 C++ 中,沒有參數的構造器稱為默認構造器。但是,出於某些原因,Java 設計者採用無參構造器這個名稱,我(作者)認為這種叫法笨拙且沒必要,所以我打算繼續使用默認構造器。Java 8 引入了 default 關鍵字修飾方法,所以算了,還是用無參構造器的叫法吧。


2.3


構造器的好處


提高了代碼可讀性。從概念上講,初始化與創建是相互獨立的。而在前面的代碼中,卻看不到對初始化方法的顯式調用。在 Java 中,對象的創建和初始化是捆綁在一起的概念,二者密不可分。


構造器是一種特殊的方法,因為它沒有返回值。但它和返回類型為 void 的普通方法不同,普通方法可以返回空值,但還是能選擇讓它返回別的值。而構造器沒有返回值,也沒有給你選擇的機會(雖然 new 表達式返回了剛創建的對象的引用,但構造器本身卻是沒有返回任何值的)。


試想一下,如果它真的有返回值,並且你也可以自己選擇讓它返回什麼,那麼編譯器還得知道接下來該怎麼處理那個返回值(這個返回值沒有接收者)。


3 方法重載


名稱是編程語言都具備的一個重要特性。當你創建一個對象時,就會給此對象分配的內存空間一個名稱。一個方法就是一種行為的名稱。通過名稱引用所各種對象,屬性和方法。良好的命名可以讓系統易於理解和修改。


在將人類語言映射到編程語言時,拷問靈魂的問題就來了。因為通常來說,一個詞可以表達多種不同的含義——它們被"重載"了!


3.1


人類語言場景


尤其是當含義差別很小時,這會很有用。就好像正常人會說"洗襯衫"、"洗車"和"洗狗"。而如果硬要這麼說就會顯得很愚蠢:"以洗襯衫的方式洗襯衫"、"以洗車的方式洗車"和"以洗狗的方式洗狗",因為聽眾根本不需要明確區分行所執行的動作。大多人類語言都具有這種"冗餘"性,即使漏掉幾個詞,你也能明白含義。不需要對每個概念都使用不同的詞彙——可以從上下文推斷(基於大家都是智商正常的)。


3.2


編程語言場景


大多數編程語言(尤其是 C )要求為每個方法(在這些語言中經常稱為函數)提供一個獨一無二的標識符。所以,你可別指望有一個萬金油 print() 函數能打印整型,也能打印浮點型——每個函數名都必須不同。


在 Java 和 C++ 中,還有一個因素促使了必須使用方法重載:構造器。因為構造器名肯定與類名相同,所以一個類中只會有一個構造器名。

那麼問題又來了:怎麼通過多種方式創建一個對象?

都是構造器,所以肯定名稱相同——就是類名。因此,方法重載就很必要了:允許方法具有相同名稱,但不同類型的參數。


3.3

區分方法重載


方法名相同,Java怎麼知道你調用的是哪個?

最好最簡單的實現只需遵循:每個被重載的方法必須有獨一無二的參數類型列表。雖然也可以根據參數順序來區分,但這會造成代碼難以維護。


3.4


重載與基本類型


基本類型會自動從較小類型轉型為較大類型。當這與重載結合時,有時令人迷糊。如果傳入的參數類型(比如 int)大於方法期望接收的參數類型(byte),你必須首先做窄化轉換,否則編譯器就會報錯。


3.5


返回值的重載


初學者經常搞不懂為什麼就不能通過方法返回值區分呢?

看如下兩個方法,它們有相同的命名和參數,但是很容易區分:

<code>void f(){}int f() {return 1;}/<code>

有時,編譯器很容易從上下文推斷出該調用哪個方法,如下

<code>int x = f()/<code>

但是,其實是可以操作調用一個方法且忽略返回值。這叫做調用一個函數的副作用,因為你不在乎返回值,只是想利用方法做些事。

所以如果你直接調用 f(),Java 編譯器就不知你到底想調用誰,閱讀者也不明所以。基於此,所以你不能根據返回值類型區分重載的方法。為了支持新特性,雖然 Java8 在一些具體情形下提高了猜測的準確度,但通常來說並無卵用。


4 無參構造器


一個無參構造器就是不接收參數的構造器,用來創建一個"默認的對象"。

  • 如果你創建一個類,類中沒有構造器,那麼編譯器就會自動為你創建一個無參構造器
  • 但是,如果你顯式定義了構造器(無論有參還是無參),編譯器就不會再自動為你創建無參構造器編譯器認為你已經寫了構造器,所以肯定知道你自己在做什麼,如果你自己沒有創建默認構造器,說明你本就不需要。


5 this 關鍵字


兩個相同類型的對象 a 和 b,你可能在想,編譯器是如何知道該為哪個對象調用方法的呢?

<code>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);    }}/<code>


編譯器做了優化,其實在方法中第一個參數,就已經隱密地傳入了一個指向所操作對象的引用。

<code>Banana.peel(a, 1)Banana.peel(b, 1)/<code>

這是內部實現的,SE不可以直接這麼寫代碼。


假設在方法內部,你想獲得對當前對象的引用。但是,引用是被秘密傳給編譯器的,而並不在參數列表中。方便的是,有一個關鍵字: this 。


this 關鍵字只能在非static方法內使用。當你調用一個對象的方法時,this 生成了一個對象引用。你可以像對待其他引用一樣對待這個引用。 如果你在一個類的方法裡調用其他該類中的方法,不要使用 this,直接調用即可,this 自動地應用於其他方法上了。


5.1


適用場景


this 關鍵字只用在一些必須顯式使用當前對象引用的特殊場合。例如:

  1. 在建造者模式中,在 return 語句中返回對當前對象的引用
  2. 參數列表中的變量名 s 和成員變量名相同,會引起混淆。可以通過 this.var
  3. 向其他方法傳遞當前對象class Person { public void eat(Apple apple) { Apple peeled = apple.getPeeled(); System.out.println("Yummy"); }}​public class Peeler { static Apple peel(Apple apple) { // ... remove peel return apple; // Peeled }}​public class Apple { Apple getPeeled() { return Peeler.peel(this); }}​public class PassingThis { public static void main(String[] args) { new Person().eat(new Apple()); }}​Apple 因為某些原因(比如說工具類中的方法在多個類中重複出現,你不想代碼重複),必須調用一個外部工具方法 Peeler.peel() 做一些行為。必須使用 this 才能將自身傳遞給外部方法。
  4. 構造器中調用構造器一個類中有多個構造器,為避免代碼重複,想在一個構造器中調用另一個構造器來。可以使用 this。通常 this,意味著"這個對象"或"當前對象",它本身生成對當前對象的引用。在構造器中,當給 this 一個參數列表時,它是另一層意思:顯式調用構造器。

5.2

再談static


之前我們就討論過 static,現在已經知道了 this 關鍵字的作用,這有助於提高對 static 修飾方法的理解。

static 方法中不會存在 this。不能在static方法中調用非static方法(反之則是可以的)。

static方法是為類而創建,無需任何實例。這其實就是static方法的主要目的,static方法看起來就像全局方法,但是 Java 不允許全局方法,一個類中的靜態方法可以被其他的靜態方法和靜態屬性訪問。


note:一些人認為static方法破壞面向對象,因為它們具有全局方法語義。使用靜態方法,因為不存在 this,所以你沒有向一個對象發送消息。的確,如果你發現代碼中出現了大量的 static 方法,就該重新考慮自己的設計了。然而,static 的概念很實用,許多時候都要用到它。至於它是否真的"面向對象",就留給理論家討論吧。


分享到:


相關文章: