1. 程式人生 > >java類的初始化

java類的初始化

1、構造方法其實就是一個隱式的static方法(thinking in java),構造器是一種特殊的static方法。

      如果是從導致類載入的角度來看,那麼算static的,因為訪問類的static方法或static屬性,或者呼叫建構函式會導致類被載入。
      如果從static方法內部不能呼叫非static方法來說,構造器裡面又能呼叫非static方法

2、當我們指定了其他的構造方法,而整個類有且僅有一個這個構造方法:編譯器將不允許你以其他任何方式建立沒有在類定義的構造器物件

      相當於我們對類講:我已經為你定義了構造你的方法,除了這個方法以外,你不需要用其他的方法去創造物件,即使它曾經是預設構造器

3、可以在一個構造方法裡面呼叫另外一個構造方法,但是禁止在其他地方呼叫構造方法

4、重構:重構就是在不改變軟體現有功能的基礎上,通過調整程式程式碼改善軟體的質量、效能,使其程式的設計模式和架構更趨合理,提高軟體的擴充套件性和維護性。

      過載: 在一個類定義中,可以編寫幾個同名的方法,但是隻要它們的簽名引數列表不同,Java就會將它們看做唯一的方法。簡單的說,一個類中的方法與另一個方法同名,但是引數表不同,這種方法稱之為過載方法。不能使用返回值作為評判標準,因為當你呼叫的時候,沒有指定返回值,編譯器不知道你要呼叫哪一個

      重寫:即把父類的方法覆蓋了,重新實現;即是同一個函式

      參考文章:http://blog.csdn.net/u011031854/article/details/11570885

5、成員的初始化,如果沒有指定值,我們的編譯器會在你建立成員變數的地方為他們提供初始化的值

6、java編譯器在處理類的初始化的時候其實是經過三個階段:

(1、載入:由類載入器執行,查詢位元組碼並從位元組碼建立一個class物件(可以這樣子說:類在初次使用的時候才會發生載入

(2、連結:在連結階段將驗證類中的位元組碼,為靜態區域分配儲存空間,並且如果必須的話,將解析這個類建立的對其他類的所有引用

(3、初始化:首先初始化靜態塊、靜態變數、靜態方法,如果該類有超類,則對其初始化

在載入過程中,類的初次使用有如下幾種形式:
1) 建立類的例項
2) 訪問某個類或者介面的靜態變數,或者對該靜態變數賦值(如果訪問靜態編譯時常量(即編譯時可以確定值的常量)不會導致類的初始化)
3) 呼叫類的靜態方法
4) 反射(Class.forName(xxx.xxx.xxx))
5) 初始化一個類的子類(相當於對父類的主動使用),不過直接通過子類引用父類元素,不會引起子類的初始化
6) Java虛擬機器被標明為啟動類的類(包含main方法的)
類與介面的初始化不同,如果一個類被初始化,則其父類或父介面也會被初始化,但如果一個介面初始化,則不會引起其父介面的初始化。

接下來就是對空間的分配,但此時僅僅是分配空間並把所有記憶體置為0

在初始化的過程中,在同一個概念層次來說,即static層次、普通成員變數層次等,變數定義的先後順序決定了初始化的先後順序

public class House {
	public House(){
		System.out.println("In the house:");
		Dog dog6 = new Dog(6);
	}
	static {
		Dog dog5 = new Dog(5);
	}
	private Dog dog7 = new Dog(7);
	private static Dog dog1 = new Dog(1);//在dog1、2、3這幾個物件,如果沒有new,物件是不會被初始化的
	private static Dog dog3 = new Dog(3);
	private static Dog dog2 = new Dog(2);
	
	public static void createDog(){
		Dog dog4 = new Dog(4);
	}
}//初始化順序是5 1 3 2 7 6

其次,我們能發現靜態塊是會被初始化,嘗試把靜態塊語句放到後面,輸出的順序也會變,就是說靜態塊的初始化符合“變數定義的先後順序決定了初始化的先後順序”這個原則。如果類裡面有形如static final修飾的值,我們稱之為編譯時常量,直接呼叫   類名.值   的方式訪問不會初始化類

構造方法實質上是一個特殊的靜態方法,但是構造方法會在所有的變數初始化後進行初始化,而普通成員變數會在static成員變數初始化之後初始化

所以初始化順序:靜態物件——非靜態物件——構造方法

假設有一個Dog的類:

(1、即使沒有顯式的使用static關鍵字,構造器實際上也是靜態方法,因此,在首次創造Dog物件額時候,或者Dog類的靜態方法/靜態域首次被訪問,java直譯器必須查詢類路徑,定位Dog.class
(2、載入Dog.class,有關靜態初始化的所有動作都會執行,因此,靜態初始化只有在class物件首次載入的時候進行一次
(3、當使用new Dog()來建立物件的時候,首先在堆裡為Dog物件分配足夠的儲存空間
(4、儲存空間會清零,這就自動的將Dog物件的所有基本型別資料設定為預設值,引用為null

(5、執行出現在定義處的初始化動作

(6、執行構造器

參考:

http://blog.csdn.net/zhengzhb/article/details/7517213

7、為了更好理解初始化順序,引用一道阿里巴巴的面試題:http://my.oschina.net/chape/blog/299533?fromerr=G7bNLvjy

public class InitializeDemo {
	private static int k = 1;
	private static InitializeDemo t1 = new InitializeDemo("t1");
	private static InitializeDemo t2 = new InitializeDemo("t2");
	private static int i = print("i");
	private static int n = 99;
	static {
		print("靜態塊");
	}
	private int j = print("j");
	{
		print("構造塊");
	}
	public InitializeDemo(String str) {
		System.out.println((k++) + ":" + str + "   i=" + i + "    n=" + n);
		++i;
		++n;
	}
	public static int print(String str) {
		System.out.println((k++) + ":" + str + "   i=" + i + "    n=" + n);
		++n;
		return ++i;
	}
	public static void main(String args[]) {
		new InitializeDemo("init");
	}
}
我們可以看到他的輸出結果:

1:j   i=0    n=0

2:構造塊   i=1    n=1

3:t1   i=2    n=2
4:j   i=3    n=3
5:構造塊   i=4    n=4
6:t2   i=5    n=5
7:i   i=6    n=6
8:靜態塊   i=7    n=99
9:j   i=8    n=100
10:構造塊   i=9    n=101
11:init   i=10    n=102

在main方法開始載入這個類的時候,所有的static屬性的成員、方法都已經在連結階段分配好儲存控制元件並且進行預設值的初始化,也就是說,那些static的k、i、n全是0

記住一個原則:順序執行初始化

第一步:k=1出來了,但是並沒有任何列印資訊

第二步:初始化t1,注意static的那些變數只有一份,而且他們的空間已經有了,都是0,這時候我們只需要考慮t1自己的變數和構造方法,這時候就是j,於是輸出j   i=0    n=0

第三步:t1的成員變數初始化完畢,輪到他自己的構造塊和構造方法輸出構造塊   i=1    n=1t1   i=2    n=2

第四步:t1初始化完畢,輪到t2,按照上面的思路,同樣t2的j是他自己的,先把j輸出來:j   i=3    n=3,同理是構造塊和構造方法:構造塊   i=4    n=4和t2   i=5    n=5

第五步:這時候t1、t2搞定,回來到本體,輪到i:i   i=6    n=6

後面的就不寫了,順序執行,一個道理,到了這裡大家也不難明白

再看看另外一道:http://topic.csdn.net/u/20120222/22/dc082753-6298-4709-ba5a-a6df55c3a207.html

public class Test{  
    private static Test tester = new Test();  
    private static int count1;                 
    private static int count2 = 2;            
    public Test(){                            
        count1++;  
        count2++;  
        System.out.println("" + count1 + count2);  
    }  
    public static Test getTester(){          
        return tester;  
    }  
      
    public static void main(String[] args){  
       Test.getTester();  
    }  
}
輸出1 1

只是輸出1 1,但其實執行下來count1 = 1,count2 = 2;

這個也不難理解

8、型別資訊

public class Initable {
	static final int staticFinal = 47;
	static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
	static{
		System.out.println("Initializing Initable");
	}
}

public class Initable2 {
	static int staticNonFinal = 147;
	static{
		System.out.println("Initializing Initable2");
	}
}

public class Initable3 {
	static int staticNonFinal = 74;
	static{
		System.out.println("Initializing Initable3");
	}
}

public class ClassInitialization {
	public static Random rand = new Random(47);
	public static void main(String[] args) throws ClassNotFoundException {
		// TODO Auto-generated method stub
		Class initable = Initable.class;
		System.out.println("After creating Initable ref");
		System.out.println(Initable.staticFinal);
		System.out.println(Initable.staticFinal2);
		
		System.out.println(Initable2.staticNonFinal);
		
		Class initable3 = Class.forName("com.example.test.Initable3");
		System.out.println("After creating Initable3 ref");
		System.out.println(Initable3.staticNonFinal);
	}
}

執行輸出:

After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable3 ref
74

編譯時常量static final int initable = 47; 訪問的時候並不會發生初始化

9、繼承初始化順序:基類—— 子類

public class Insect {
	private int i = 9;
	protected int j;
	public Insect() {
		System.out.println("i="+i+" j="+j);
		j=39;
	}
	private static int x1 = printInit("static Insect x2 initialized");
	static int printInit(String s){
		System.out.println(s);
		return 47;
	}
}

public class Beetle extends Insect {
	private int k = printInit("Beetle k initialized");
	public Beetle() {
		System.out.println("k="+k);
		System.out.println("j="+j);
	}
	private static int x2 = printInit("static Beetle x2 initialized");
	public static void main(String[] args) {
		System.out.println("Beetle constructor");
		Beetle b = new Beetle();
	}
}
執行結果:

static Insect x2 initialized
static Beetle x2 initialized
Beetle constructor
i=9 j=0
Beetle k initialized
k=47
j=39

因為main方法在bettle類裡面,在使用載入beetle的過程中,編譯器注意到他並不是基類,於是去尋找基類,得到Insect後:

1)、先按照初始化順序初始化static成員,輸出static Insect x2 initialized,然後子類的static成員被初始化(原因是子類的static成員可能依賴於父類的static成員是否能被正確的初始化)

2)、由於本例是因為在載入main的時候就需要對insect初始化,並未執行new Bettle(),所以會先輸出Beetle constructor,但是如果把main放在另外的新類,就會最首先輸出Beetle constructor這句話。言歸正傳,初始化父類的普通成員變數後接下來就是父類的構造方法,然後子類的普通成員變數,子類的構造方法

所以:父類靜態成員——子類靜態成員——父類普通成員以及構造方法——子類普通成員以及構造方法

關於介面:在接口裡面的域是隱式的static和final的,會在類第一次載入的時候被初始化

在繼承裡面,我們可以看看這一種情況:

public class Glyph {
	void draw(){
		System.out.println("Glyph.draw");
	}
	public Glyph() {
		System.out.println("Glyph before draw");
		draw();
		System.out.println("Glyph after draw");
	}
}

public class RoundGlyph extends Glyph {
	private int radius = 1;
	public RoundGlyph(int r) {
		System.out.println("RoundGlyph,radius = "+radius);
	}
	void draw(){
		System.out.println("RoundGlyph,radius = "+radius);
	}
}
如果new一下子類物件
執行輸出:

Glyph before draw
RoundGlyph,radius = 0
Glyph after draw
RoundGlyph,radius = 1

我們可以發現,當呼叫被覆蓋的方法時,該方法仍未被初始化,未初始化的成員是預設值,這也驗證了上面那道面試題在初始化時成員會先開闢空間並且提供預設值這種情況,其實,java程式設計思想裡面也詳細描述了上面這個程式碼的過程(這樣的優點是所有東西能在呼叫前至少初始化成0):

(1、在其他任何事物發生之前,將分配給物件的儲存空間初始化成二進位制的零

(2、如前所述那樣呼叫基類的構造器,此時,呼叫被覆蓋後的draw()方法,在呼叫RoundGlyph構造器之前呼叫,由於步驟一,我們會發現radius為0

(3、按照宣告的順序初始化

(4、呼叫匯出類(子類)的構造器主體

10、簡單提及一下java的垃圾回收:我們都知道java有一個垃圾回收器,不需要程式設計師手動清理多餘的記憶體,關於垃圾回收器仍然有不少細節,例如:

1、垃圾回收器只知道釋放那些被new(堆)出來的記憶體,而不會回收其部分的記憶體。

2、垃圾回收器不一定釋放沒用到的物件

3、回收之前會執行finalize()方法,但是這不意味者在finalize()裡面做清理工作——因為你不知道他什麼時候要回收,也意味著清理工作的執行時間你不可控

4、停止——複製:垃圾回收器回收策略之一,先暫停程式的執行,然後把存活的物件複製一份到另外一個堆,沒有被複制的全部是垃圾,在這種方法效率不高,除了複製動作浪費時間,還浪費空間

5、標記——清掃:遍歷所有的引用,尋找存活的物件,加以標記,當遍歷完成之後,把沒有標記的物件釋放,但是剩下的空間是不連續的

6、這兩種方式是結合使用的,統稱“自適應”技術,java虛擬機器以“塊”來處理這些事,記憶體大的單獨放在一個塊,小的多個放在一個塊,用代數來記錄他是否存活,被引用了代數則增加,很明顯,小的物件使用複製的方法整理,而大的物件不需要複製,只會增加代數