1. 程式人生 > >Java程式設計思想 初始化與清理

Java程式設計思想 初始化與清理

初始化與清理

1.構造器
  • 名稱:

構造器的名稱必須與類名完全相同,所以“每個方法首字母小寫”並不適用於構造器。

  • 返回值

構造器是沒有返回值,這與返回值為空(void)不同。對於空返回值,儘管方法本設計不會自動返回,但是仍可以選擇讓它返回別的東西。但是構造器不會返回任何東西。

  • 方法過載

為實現多種方式建立一個物件,就要有多個同名的構造器。構造器是強制過載方法名的一個原因。

基本型別的過載涉及到窄化轉換和擴充套件轉換。

區分過載方法:每個過載的方法都必須有一個獨一無二的引數列表。不能以返回值的型別區分。

  • 預設構造器

預設構造器是沒有形式引數的。如果已經定義了一個構造器,編譯器就不會幫忙自動建立預設構造器。

2.this關鍵字

-在方法內部獲得對當前物件的呼叫

在一條語句裡對一個物件執行多次操作

public class leaf {
    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();
        //this傳遞x到下一個方法中,使得x.increment()連續呼叫三次
} }//輸出為i = 3

將自身傳遞給外部方法

class Peeler {
    static Apple peel(Apple apple){
        return apple;
    }
}
class Apple {
    Apple getPeeled() {
        return Peeler.peel(this);
    }
}
  • 在構造器中呼叫構造器、防止資料成員與引數混淆

只能用this呼叫一個構造器,必須將構造器置於最起始處。

public class Flower{
    int petalCount = 0;
    String s = "initial value"
; Flower(int petals) { petalCount = petals; } Flower(String ss) { s = ss; } Flower(String s, int petals){ this(petals);//呼叫第一個構造器,不能再放第二個,放在最起始處 this.s = s;//用this.s訪問資料成員,避免與引數混淆 } }
3.垃圾回收機制
  • finalize()方法

工作原理:一旦垃圾回收器準備好釋放物件佔用的儲存空間,將首先呼叫finalize()方法,並且在下一次垃圾回收動作發生時,才會真正回收物件佔用的記憶體。

無論物件時如何建立的,垃圾回收器都會負責釋放物件佔據的所有記憶體,所以對finalize()的需求限制到,通過某種建立物件以外的方式為物件分配了儲存空間。不過這種情況一般發生在使用“本地方法”的情況下,本地方法是一種在Java中呼叫非Java程式碼的方式。

終結條件:可以用finalize()發現一些隱藏的缺陷。System.gc()用於強制進行終結動作,不一定真的可以觸發finalize()finalize()用於驗證終結條件。

  • 無論是垃圾回收還是finalize()方法都是不可控的
  • 垃圾回收器的工作方式

引用計數:每個物件都含有一個引用計數器,當有引用連線至物件時,計數器加一;當引用離開作用域或者被置為NULL時,計數器減一。當發現某個物件的引用計數為0時,就釋放其空間。這種方法無法回收存在迴圈引用的物件。

停止複製:對任何活的物件,都一定能最終追溯到存活在堆疊或靜態儲存區之中的引用。具體做法是,先暫停程式的執行,將所有存活的物件從當前堆移到另一個堆,沒有被複制的都是垃圾。另外所有指向物件的引用必須要修正。(比較浪費記憶體,不適用於當程式處於穩定狀態,只有少量垃圾的情況)

標記清掃:同樣從堆疊和靜態儲存區出發,遍歷所有的引用,找出所有存活的物件。但是它會對所有存活的物件進行標記。只有在全部標記工作完成後,清理動作才會開始。沒有標記的物件將被釋放,剩下的堆空間將是不連續的。垃圾回收期可能需要重新整理剩下的物件以得到連續的空間。

Java虛擬機器中,記憶體以較大的塊為單位。每個塊由相應的代數(generation count)來記錄是否存活。在垃圾回收器進行整理過程中,大型物件不會被複制,只是代數會改變;內含小型物件的塊則被複制和整理。

Java虛擬機器會跟蹤標記清掃的效果,即堆斷裂的情況,決定何時切換回停止複製。

4.初始化

類的資料成員中基本型別都會被初始化。區域性變數(定義在方法內部的變數)通過編譯時出錯保證變數初始化。

可以在定義類成員變數的地方為其賦值。(C++不允許)

可以由預設構造器進行初始化,也可以自己定義構造器使其初始化。

也可以通過呼叫某個方法提供初始值。

public class MethodInit2 {
    int i = f();
    int j = f(i);//上下兩句不能互換位置,必須i先被初始化才能給j賦值
    int f() { return 11; }
    int g(int n) { return n * 10; }
}
  • 初始化順序:
static關鍵字先初始化,只初始化一次。再是變數初始化。最後呼叫方法或者構造器。
class Bowl {
  Bowl(int marker) {
    System.out.println("Bowl(" + marker + ")");
  }
  void f(int marker) {
    System.out.println("f(" + marker + ")");
  }
}

class Table {
  static Bowl b1 = new Bowl(1);//第一步,僅一次
  Table() {
    System.out.println("Table()");
    b2.f(1);
  }
  void f2(int marker) {
    System.out.println("f2(" + marker + ")");
  }
  static Bowl b2 = new Bowl(2);//第二步,僅一次
}

class Cupboard {
  Bowl b3 = new Bowl(3);//第三步
  static Bowl b4 = new Bowl(4);//第一步,僅一次
  Cupboard() {
    System.out.println("Cupboard()");
    b4.f(2);
  }
  void f3(int marker) {
    System.out.println("f3(" + marker + ")");
  }
  static Bowl b5 = new Bowl(5);//第二步,僅一次
}

public class StaticInitialization {
  public static void main(String[] args) {
    System.out.println(//三及以後
      "Creating new Cupboard() in main");
    new Cupboard();
    System.out.println(
      "Creating new Cupboard() in main");
    new Cupboard();
    t2.f2(1);
    t3.f3(1);
  }
  static Table t2 = new Table();//第一步
  static Cupboard t3 = new Cupboard();//第二步
} 

輸出結果:

Bowl(1)//介面中第一個static,Table()第一次構造
Bowl(2)
Table()
f1(1)
Bowl(4)//介面中第二個static,Cupboard()第一次構造
Bowl(5)
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard() in main
Bowl(3)//Cupboard()第二次構造
Cupboard()
f1(2)
Creating new Cupboard() in main
Bowl(3)//Cupboard()第三次構造
Cupboard()
f1(2)
f2(1)
f3(1)
  • 物件建立過程:

假設有個名為Dog()的類。首次建立Dog()的物件時(構造器可看作靜態方法),或者Dog類的靜態方法/靜態域首次被訪問時,Java直譯器必須查詢類的路徑,定位Dog.class檔案。

載入Dog.class,完成靜態初始化。靜態初始化只在Class物件首次載入時進行一次。

new Dog()建立物件時,首先在堆上為Dog物件分配足夠的儲存空間。

儲存空間將被清零,Dog資料成員中所有基本資料型別都設定成預設值,引用被設成NULL。

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

執行構造器。

5.靜態資料初始化和非靜態例項初始化
  • 靜態資料初始化
  static {
    c1 = new Cup(1);
    c2 = new Cup(2);
  }
  • 靜態例項初始化
  {
    c1 = new Mug(1);
    c2 = new Mug(2);
    System.out.println("c1 & c2 initialized");
  }
  • 初始化順序

靜態資料、靜態資料塊按照在類中位置的先後順序依次進行初始化,未建立物件也會初始化,而且只初始化一次。只有在建立物件後,(在靜態資料初始化後)才進行例項塊初始化,最後再進行構造器初始化。

6.陣列初始化
  • 基本型別初始化
int[] a2;
int a1[] = { 1, 2, 3, 4, 5 };//直接初始化
a2 = a1;//只是複製了引用
int[] a = new int[rand.nextInt(20)];
//用new建立基本型別陣列,陣列的大小小於20隨機決定,資料元素中基本資料型別值會自動初始化為空值
  • 陣列的列印方法
import java.util.*;
System.out.println(Arrays.toString(a));//提供a的列印方法
  • 非基本型別初始化
Integer[] a = new Integer[rand.nextInt(20)];//目前只是一個空引用,還沒有指向物件
for(int i = 0; i < a.length; i++)
    a[i] = rand.nextInt(69);//直到建立了物件,並將物件賦值給了引用,初始化才算結束

或者 陣列的聚集初始化

Integer[] a = {
    new Integer(1),
    new Integer(2),
    3,//該,可有可沒有
}
public class VarArgs {
  static void f(Object[] x) {
    for(int i = 0; i < x.length; i++)
      System.out.println(x[i]);
  }
  public static void main(String[] args) {
    f(new Object[] { 
        new Integer(47), new VarArgs(), 
        new Float(3.14), new Double(11.11) });//直接在傳輸時定義了陣列
    f(new Object[] {"one", "two", "three" });
    f(new Object[] {new A(), new A(), new A()});
  }
}