Java基本功——初始化與清理

1、搞懂this關鍵字的前因後果

先上一段代碼:java

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

public class EG {
	public static void main(String[] args) {
		Banana a = new Banana();
		Banana b = new Banana();
		a.peel(1);
		b.peel(2);

	}
}

咱們能夠看到 Banana的兩個對象a 和 b 都在調用Banana的方法peel(),可是編譯器是如何區分到底是a調用了peel呢?仍是b調用的peel呢?app

由此,編譯器在幕後作了一些工做,把「操做對象的引用」暗自傳遞給peel()方法,來區別是誰調用了peel()。因此事實上是這樣調用的:jvm

Banana.peel(a, 1);函數

Banana.peel(b, 2);this

若是你想在方法的內部得到調用這個方法的引用,由於是編譯器偷偷傳入的,因此無法表示這個引用。爲此,有個專門的關鍵字thisspa

此處有段代碼爲證:code

public class thisTest {
	public void instanceMethod(Object args) {
		System.out.println(this);
	}
	
	public static void main(String[] args) {
		thisTest t = new thisTest();
		System.out.println(t);
		t.instanceMethod("");
	}
}

能夠看到兩次輸出都是同一個t對象的信息:orm

init.thisTest@15db9742
init.thisTest@15db9742對象

所以,this只能在方法內部使用,表示對「調用方法的那個對象」的引用繼承

可是也不必到處使用this,將this放在不必的地方只會讓你的代碼變得複雜難懂。以下:
 

public class Apricot {

	void pick() {
		/** ... **/
	}

	void pit() {
		pick();
		/** ... **/
	}
}

在pit()中,咱們徹底能夠寫 this.pick()但沒有必要,編譯器能幫你自動添加。

1.1 屢次調用

只有當須要明確指出對當前對象的引用的時候,才須要使用this關鍵字。例如當須要返回對當前對象的引用時:

/**
 * Simple user of the "this" keyword
 * 
 * @author kfh
 *
 */

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 l = new Leaf();
		// 我不由感嘆 這調用真tm精髓~
		l.increment().increment().increment().print();
	}
}

方法increment 用this關鍵字返回了調用它的對象,這使得在一條語句中屢次調用該對象的方法變得很簡單。  

1.2 本身調本身

再看下面的這個例子,意味深長:

class Person {
	public void eat(Apple apple) {
		Apple peeled = apple.getPeeled();
		System.out.println("Yummy!");
	}
}

class Apple {
	Apple getPeeled() {
		// 此處調用getPeeled()方法,必須用this將調用getPeeled方法的apple傳進去
		// 至關於這句話 apple.getPeeled(); 是將apple本身傳到了getPeeled裏面,把本身削了皮
		return Peeler.peel(this);
	}
}

class Peeler {
	static Apple peel(Apple apple) {
		System.err.println("remove apple peel...");
		return apple;// Peeled
	}
}

public class PassingThis {
	public static void main(String[] args) {
		new Person().eat(new Apple());
	}
}

1.3 調用構造器

有時候一個類會有多個構造方法,當在一個構造方法中調用另外一個構造方法時,就用this。一般寫「this」的時候,表示這個對象或者當前對象,它自己則表示對當前對象的引用。若是this後面跟上了參數列表,則表示對某個構造函數明確調用

看下面的例子:

public class Flower {
	int petalCount = 0;
	String s = "initial value";

	Flower(int petals) {
		petalCount = petals;
		print("Constructor w/ int arg only, petalCount = " + petalCount);
	}

	Flower(String ss) {
		print("Constructor w/ int arg only, s = " + ss);
	}

	Flower(String s, int petals) {
		this(petals);
		this.s = s; // 因爲參數s的名稱和數據成員s的名稱相同,因此用this.s 表明數據成員
		print("String & int args");
	}

	Flower() {
		this("hi", 47);
		print("default constuctor (no args)");
	}

	void printPetalCount() {
		print("petalCount = " + petalCount + " s = " + s);
	}
	
	public static void main(String[] args) {
		Flower x = new Flower();
		x.printPetalCount();
	}

	void print(Object o) {
		System.out.println(o);
	}

}

說明一下,這裏Flower x = new Flower(); 首先調用了無參的構造

到這:

Flower() {
        this("hi", 47);
        print("default constuctor (no args)");
    }

而後this("hi", 47) 則調用的是倆參 (String, int)的構造因此到了這裏:

Flower(String s, int petals) {
        this(petals);
        this.s = s; // 因爲參數s的名稱和數據成員s的名稱相同,因此用this.s 表明數據成員
        print("String & int args");
    }

而後this(petals) 調的是Flower(int) 的構造 到了這裏:

Flower(int petals) {
        petalCount = petals;
        print("Constructor w/ int arg only, petalCount = " + petalCount);
    }

因此輸出結果是:

Constructor w/ int arg only, petalCount = 47
String & int args
default constuctor (no args)
petalCount = 47 s = hi

因此先輸出int構造,而後是倆參構造,最後是無參構造。

可是須要注意的是:1.只能在構造方法中用this調用構造方法,不能在非構造方法中用this調構造方法。2. 用this調構造必須寫在構造方法的第一行

1.4  static代碼中無this?

什麼是static方法?static方法就是沒有this的方法。爲何呢?  你們回憶一下關於this的用法,忘記的能夠向前翻一下。
是在方法的內部得到對當前對象的引用,這個引用是編譯器在調用方法的時候「偷偷」做爲第一個參數傳入的,因此沒有變量表示它,用this表示。
由於static方法是在沒有建立對象的前提下,僅僅經過類自己來調用static方法,這樣編譯器沒有將對象的引用傳入方法中,因此無法用this來得到當前對象的引用。

 

1.5 有關finalize()

java中的清理工做都是gc來完成的,其中咱們能夠看到的則是這個繼承自Object類的私有方法finalize();

 

什麼狀況下會調用finalize()呢?有以下三種狀況

1.全部對象被Garbage Collection時自動調用,好比運行System.gc()的時候.

2.程序退出時爲每一個對象調用一次finalize方法。

3.顯式的調用finalize方法

除此之外,正常狀況下,當某個對象被系統收集爲無用信息的時候,finalize()將被自動調用,可是jvm不保證finalize()必定被調用,也就是說,finalize()的調用是不肯定的,這也就是爲何sun不提倡使用finalize()的緣由. 

且看下面的例子:

/**
 * Create a class with a finalize() method that prints a message. In main(),
 * create an object of your class. Explain the behavior of your program.
 * 
 * @author kfh
 *
 */
class Bank {
	boolean loggedIn;

	Bank(boolean loggedStatus) {
		this.loggedIn = loggedStatus;
	}

	void logIn() {
		loggedIn = true;
	}

	void logOut() {
		loggedIn = false;
	}

	// finalize()方法是屬於一個類的,在gc清理以前買足有引用須要清理的時候gc自動調用
	protected void finalize() {
		if (loggedIn) {
			System.out.println("ERROR: still logging in!");
			// Normally, you'll also do this:
			// super.finalize(); // Call the base-class version
		}
	}
}

public class Ex10 {

	public static void main(String[] args) {
		Bank b1 = new Bank(true);// 第一個bank登錄
		b1.logOut();
		Bank b2 = new Bank(true);// 第二個bank登錄
		new Bank(false); // 只有當存在這句話的時候,類中的finalize()方法纔會被觸發
		/**
		 * new Bank(true) => 輸出:ERROR: still logging in!
		 */
		System.gc();
	}
}

2. 初始化

有關初始化我要說的是: 類的成員是會在調用該類的任何非靜態方法(包括構造器)被調用以前的到初始化的。

注:其實類的成員初始化是在調用該類的構造器以前完成的,上面那句話是由於調用類的別的方法以前必然已經調用了該類的構造器了,可是調用static的方法就不行了哦

若是類的成員是基本類型的,會初始成一個默認的值。若是是一個對象引用時,會初始成null。

而初始化的順序是根據變量定義的順序決定的,且看下面的這個例子:

/**
 * Demonstrates initialization order <br>
 * 在類的內部,變量定義的順序決定了初始化的順序。即便變量定義散佈於方法定義<br>
 * 之間,他們仍舊會在任何方法(包括構造器)被調用以前獲得初始化。
 * 
 * @author kfh
 *
 */
// when the construtor is called to create a
// window object, you'll see a message
class Window {
	Window(int marker) {
		print("Window(" + marker + ")");
	}
}

class House {
	Window w1 = new Window(1); // Before constructor

	House() {
		// Show that we're in the constructor;
		print("House()");
		new Window(33);

	}

	Window w2 = new Window(2);// After constructor

	void f() {
		print("f()");
	}

	Window w3 = new Window(3);// At end
}

public class InitOrder {
	public static void main(String[] args) {
		House house = new House();
		house.f();
	}
}

/**
 * 因此此處的輸出是這樣的:<br>
 * Window(1) <br>
 * Window(2) <br>
 * Window(3)<br>
 * House()<br>
 * Window(33)<br>
 * f()<br>
 * 
 */

 

2.1 有關static變量的初始化

請你們好好研讀這段代碼,將下面這個例子看明白了構造器和成員包括static的初始化就沒問題了:

class Bowl {
	Bowl(int marker) {
		print("Bowl(" + marker + ")");
	}

	void f1(int marker) {
		print("f1(" + marker + ")");
	}
}

class Table {
	static Bowl bowl1 = new Bowl(1);

	Table() {
		print("Table()");
		bowl2.f1(1);
	}

	void f2(int marker) {
		print("f2(" + marker + ")");
	}

	static Bowl bowl2 = new Bowl(2);
}

class Cupboard {
	Bowl bowl3 = new Bowl(3);
	static Bowl bowl4 = new Bowl(4);

	Cupboard() {
		print("Cupboard()");
		bowl4.f1(2);
	}

	void f3(int marker) {
		print("f3(" + marker + ")");
	}

	static Bowl bowl5 = new Bowl(5);
}

public class StaticInitialization {
	public static void main(String[] args) {
		print("Creating new Cupboard() in main");
		new Cupboard();
		print("Creating new Cupboard() in main");
		new Cupboard();
		table.f2(1);
		cupboard.f3(1);
	}

	static Table table = new Table();
	static Cupboard cupboard = new Cupboard();
}

/**
 * output: <br>
 * Bowl(1) <br>
 * Bowl(2) <br>
 * Table() <br>
 * f(1) <br>
 * Bowl(4) <br>
 * Bowl(5) <br>
 * Bowl(3) <br>
 * Cupboard() <br>
 * f1(2) <br>
 * Creating new Cupboard() in main <br>
 * Cupboard() <br>
 * f1(2) <br>
 * Creating new Cupboard() in main <br>
 * Cupboard() <br>
 * f1(2) <br>
 * f2(1) <br>
 * f3(1) <br>
 * 
 */


靜態成員的初始化只有在必要的時候纔會發生,若是將上面這段代碼改爲這樣:

public class StaticInitialization {
	public static void main(String[] args) {
		print("Creating new Cupboard() in main");
		new Cupboard();
		print("Creating new Cupboard() in main");
		new Cupboard();
		// table.f2(1);
		cupboard.f3(1);

		

	}

	// static Table table = new Table();
	static Cupboard cupboard = new Cupboard();

}

bowl1 和 bowl2將永遠不會被初始化,只有在第一個Table對象被建立的時候,或者第一次訪問靜態數據的時候,它們纔會被初始化。

至此咱們應該清楚 在調用一個類的構造方法以前,會發生一些事情,好比進行初始化。

咱們來總結一下建立對象的過程(其中夾雜了初始化)(很是重要)

1.即便沒有顯式的使用static關鍵字,構造器實際上也是靜態方法。所以當首次建立類型爲Dog的對象時(構造器能夠當作靜態方法),或者Dog類的靜態方法/靜態域首次被訪問時,Java解釋器必須查找類路徑,以定位Dog.class文件。
2.而後載入Dog.class(後面會看到,這將建立一個Class對象),有關靜態初始化的全部動做都會執行。所以,靜態初始化只在Class對象首次加載的時候進行一次。
3.當用 new Dog()建立對象的時候,首先將在堆上爲Dog對象分配足夠的存儲空間

4. 這塊存儲空間會被清零,這就自動的將Dog對象中的全部基本類型數據都設置成了默認值,而引用則被設置成了null
5.執行全部出現於字段定義處的初始化動做(非靜態的初始化)
6.執行構造器。正如將在後面看到將涉及不少繼承的東西。(在執行構造器以前初始化就完成了

相關文章
相關標籤/搜索