thinking in java 筆記之初始化和清除

背景:“不安全”是程序設計最嚴重問題之一。“初始化”和“清除”是安全問題中的兩個。Java沿用C++“構建器”的概念,在一個對象創建之後自動調用。Java也增加了“垃圾收集器”,在資源不再需要的時候自動釋放。
1.1 用構建器自動初始化
Java使用構建器的名字和類名相同,在創建對象時,自動調用構建器。

class Rock {
  Rock() { // This is the constructor
    System.out.println("Creating Rock");
  }
}
public class SimpleConstructor {
  public static void main(String[] args) {
    for(int i = 0; i < 10; i++)
      new Rock();
  }
}

new Rock();執行時,會分配相應的存儲空間,並調用構建器。
1.2 方法重載
Java中要求每個函數都是獨一無二的。如果我們想用多種方式創建對象,那就需要多個構造器。但是構造器名字必須同類名一樣,我們可以給構造器帶不同的參數來區分,實現構造器的重載。

class Tree {
  int height;
  Tree() {
    prt("Planting a seedling");
    height = 0;
  }
  Tree(int i) {
    prt("Creating new Tree that is "
        + i + " feet tall");
    height = i;
  }
  void info() {
    prt("Tree is " + height
        + " feet tall");
  }
  void info(String s) {
    prt(s + ": Tree is "
        + height + " feet tall");
  }
  static void prt(String s) {
    System.out.println(s);
  }
}

每個重載的方法都必須採取獨一無二的自變量類型列表,包括自變量的類型和順序。但是不建議以順序區分,因爲這樣不易維護代碼。還有一點,方法的重載不能根據返回值類型來區分。比如void f(){} 和 int f(){},如果我們在調用的時候忽略了返回值,則編譯器不能識別去調用哪一個方法。
1.3 默認構造器
如果創建一個沒有構造器的類,則編譯程序會幫我們自動創建一個沒有任何變量、空方法體的默認構造器。如果我們自己定義了一個構造器,則程序不會幫我們創建默認構造器。例如:

class Bird {
  int i;
}
public class DefaultConstructor {
  public static void main(String[] args) {
    Bird nc = new Bird(); // 調用默認構造器
  }
}
class Bird {
  Bird(int i ){}
  Bird(float f){}
}
public class DefaultConstructor {
  public static void main(String[] args) {
    Bird nc = new Bird(); // 編譯器報錯,找不到相符的構造器
  }
}

1.4 this關鍵字
如果有兩個同類型的對象,分別叫作a和b,那麼您也許不知道如何爲這兩個對象同時調用一個f()方法:
class Banana { void f(int i) { /* … */ } }
Banana a = new Banana(), b = new Banana();
a.f(1);
b.f(2);
若只有一個名叫f()的方法,它怎樣才能知道自己是爲a還是爲b調用的呢?
爲了能用簡便的、面向對象的語法來書寫代碼——亦即“將消息發給對象”,編譯器爲我們完成了一些幕後工作。其中的祕密就是第一個自變量傳遞給方法f(),而且那個自變量是準備操作的那個對象的句柄。所以前述的兩個方法調用就變成了下面這樣的形式:
Banana.f(a,1);
Banana.f(b,2);
這是內部的表達形式,我們並不能這樣書寫表達式,並試圖讓編譯器接受它。但是,通過它可理解幕後到底發生了什麼情。假定我們在一個方法的內部,並希望獲得當前對象的句柄。由於那個句柄是由編譯器“祕密”傳遞的,所以沒有標識符用。然而,針對這一目的有個專用的關鍵字:this。this關鍵字(注意只能在方法內部使用)可爲已調用了其方法的那個對象生成相應的句柄。可象對待其他任何對象句柄一樣對待這個句柄。但要注意,假若準備從自己某個類的另一個方法內部調用一個類方法,就不必使用this。只需簡單地調用那個方法即可。當前的this句柄會自動應用於其他方法。所以我們能使用下面這樣的代碼:
class Apricot {
void pick() { /* … */ }
void pit() { pick(); /* … */ }
}
在pit()內部,我們可以說this.pick(),但事實上無此必要。編譯器能幫我們自動完成。this關鍵字只能用於那些特殊的類——需明確使用當前對象的句柄。例如,假若您希望將句柄返回給當前對象,那麼它經常在return語句中使用。

public class Leaf {
  private int i = 0;
  Leaf increment() {
    i++;
    return this;
  }
  void print() {
    System.out.println("i = " + i);
  }
  public static void main(String[] args) {
    Leaf x = new Leaf();
    x.increment().increment().increment().print();
  }
}

由於increment()通過this關鍵字返回當前對象的句柄,所以可以方便地對同一個對象執行多項操作。
① 使用this在構造器裏調用構造器,可以避免寫重複的代碼。通常,當我們說this的時候,都是指“當前對象”,而且它本身會產生當前對象的一個句柄。例如

public class Flower {
  private int petalCount = 0;
  private String s = new String("null");
  Flower(int petals) {
    petalCount = petals;
    System.out.println("Constructor w/ int arg only, petalCount= " + petalCount);
  }
  Flower(String ss) {
    System.out.println("Constructor w/ String arg only, s=" + ss);
    s = ss;
  }
  Flower(String s, int petals) {
    this(petals);
//!    this(s); // Can't call two!
    this.s = s; // Another use of "this"
    System.out.println("String & int args");
  }
  Flower() {
    this("hi", 47);
    System.out.println("default constructor (no args)");
  }
  void print() {
  //!    this(11); // Not inside non-constructor!
    System.out.println( "petalCount = " + petalCount + " s = "+ s);
  }
  public static void main(String[] args) {
    Flower x = new Flower();
    x.print();
  }
}

其中,構建器Flower(String s,int petals)向我們揭示出這樣一個問題:儘管可用this調用一個構建器,但不可調用兩個。這個例子也向大家展示了this的另一項用途。由於自變量s的名字以及成員數據s的名字是相同的,所以會出現混淆。爲解決這個問題,可用this.s來引用成員數據。在print()中,我們發現編譯器不讓我們從除了一個構建器之外的其他任何方法內部調用一個構建器。
②理解了this關鍵字後,我們可更完整地理解static(靜態)方法的含義。它意味着一個特定的方法沒有this。我們不可從一個static方法內部發出對非static方法的調用(註釋②),儘管反過來說是可以的。而且在沒有任何對象的前提下,我們可針對類本身發出對一個static方法的調用。事實上,那正是static方法最基本的意義。它就好象我們創建一個全局函數的等價物(在C語言中)。除了全局函數不允許在Java中使用以外,若將一個static方法置入一個類的內部,它就可以訪問其他static方法以及static字段。

②:有可能發出這類調用的一種情況是我們將一個對象句柄傳到static方法內部。隨後,通過句柄(此時實際是this),我們可調用非static方法,並訪問非static字段。但一般地,如果真的想要這樣做,只要製作一個普通的、非static方法即可。

有些人抱怨static方法並不是“面向對象”的,因爲它們具有全局函數的某些特點;利用static方法,我們不必向對象發送一條消息,因爲不存在this。

2. 清除
Java的GC只知道釋放那些有new分配的內存,對於一些“特殊”的不由new分配的內存區域,GC不知道如何釋放。爲了解決這個問題,Java提供了finalize(),工作原理是:一旦GC準備釋放對象佔用的存儲空間,它首先調用finalize(),在下一次垃圾收集過程中,回收對象的內存。所以調用finalize(),對象也不能被立即釋放掉。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章