Java基礎學習系列-Java類初始化和例項化
Java有以下幾種方式建立類物件:
- 利用new關鍵字
- 利用反射Class.newInstance
- 利用Constructor.newIntance(相比Class.newInstance多了有參和私有建構函式)
- 利用Cloneable/Object.clone()
- 利用反序列化
Constructor.newInstance不支援帶原型入參的建構函式。
呼叫Class.getConstructor()方法獲取無參預設構造Constructor時,如果使用者自定義了有參建構函式,因為此時java並不會生成預設建構函式,所以Class.getConstructor()方法因找不到無參預設建構函式而拋異常。此時需要顯示定義預設建構函式:
// Initialization.java
public class Initialization {
private int age = 2000;
private int salary = age + 1000;
private String name = "Tom";
public Initialization() {
print();
}
public Initialization(Integer salary, String name) {
print();
this.salary = salary;
this .name = name;
print();
}
/**
* Static code
*/
{
salary += 500;
}
private void print() {
System.out.println("age=" + this.age);
System.out.println("salary=" + this.salary);
System.out.println("name=" + this.name);
}
public static Initialization construct(int salary, String name) throws Exception {
Constructor<Initialization> constructorWithNoParams = Initialization.class.getConstructor();
Constructor<Initialization> constructorWithParams = Initialization.class.getConstructor(Integer.class, String.class);
return salary <= 0 || name == null ? constructorWithNoParams.newInstance() : constructorWithParams.newInstance(salary, name);
}
public Initialization deSerialize() throws Exception {
// 寫物件
ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("student.txt"));
output.writeObject(this);
output.close();
// 讀取物件
ObjectInputStream input = new ObjectInputStream(new FileInputStream("student.txt"));
return (Initialization) input.readObject();
}
}
再來看下Initialization類被編譯為.class檔案後的資訊:
public class Initialization {
private int age = 2000;
private int salary;
private String name;
public Initialization() {
this.salary = this.age + 1000;
this.name = "Tom";
this.salary += 500;
this.print();
}
public Initialization(Integer salary, String name) {
this.salary = this.age + 1000;
this.name = "Tom";
this.salary += 500;
this.print();
this.salary = salary.intValue();
this.name = name;
this.print();
}
private void print() {
System.out.println("age=" + this.age);
System.out.println("salary=" + this.salary);
System.out.println("name=" + this.name);
}
public static Initialization construct(int salary, String name) throws Exception {
Constructor constructorWithNoParams = Initialization.class.getConstructor(new Class[0]);
Constructor constructorWithParams = Initialization.class.getConstructor(new Class[]{Integer.class, String.class});
return salary > 0 && name != null?(Initialization)constructorWithParams.newInstance(new Object[]{Integer.valueOf(salary), name}):(Initialization)constructorWithNoParams.newInstance(new Object[0]);
}
public Initialization deSerialize() throws Exception {
ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("student.txt"));
output.writeObject(this);
output.close();
ObjectInputStream input = new ObjectInputStream(new FileInputStream("student.txt"));
return (Initialization)input.readObject();
}
public static void main(String[] args) throws Exception {
Initialization result = construct(0, "Paul");
}
}
1、無論例項變數還是例項程式碼塊,均遵從先父類後子類的初始化順序。
2、對例項變數直接賦值或者利用例項程式碼塊賦值,編譯器會其程式碼填充到類的建構函式中。不允許書寫順序靠前的例項程式碼初始化在其後定義的例項變數。
Java強制要求Object物件(Object是Java的頂層物件,沒有超類)之外的所有物件建構函式的第一條語句必須是超類建構函式的呼叫語句或者是類中定義的其他的建構函式,如果我們既沒有呼叫其他的建構函式,也沒有顯式呼叫超類的建構函式,那麼編譯器會為我們自動生成一個對超類建構函式的呼叫。這樣確保當前物件完成初始化前其父類已完成初始化,從而構建完整的物件。
如果預設建構函式內部呼叫了有參建構函式,僅允許在有參建構函式裡呼叫父類建構函式。
靜態程式碼塊
- 多個static按編碼順序依次處理
- static變數的申明和初始化是兩個不同的操作
- static變數在編譯期已確認值
以下程式碼是等價的:
// code list1
public class StaticInitialization {
static {
data = 1;
}
public static void main(String[] args) {
System.out.println(data);
}
private static int data = 2;
}
// code list 2
public class StaticInitialization {
private static int data;
static {
data = 1;
data = 2;
}
public static void main(String[] args) {
System.out.println(data);
}
}
code list 2的位元組碼為:
public class StaticInitialization {
public StaticInitialization();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: getstatic #3 // Field data:I
6: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
9: return
static {};
Code:
0: iconst_1
1: putstatic #3 // Field data:I
4: iconst_2
5: putstatic #3 // Field data:I
8: return
}
可以看到static變數在編譯期就已放到常量const指向的記憶體地址裡。
初始化順序
如果沒有繼承關係,初始化順序為:靜態程式碼、靜態程式碼塊 > 成員變數、例項程式碼塊 > 建構函式。否則,初始化順序為先父後子。
所以初始化順序:父類static靜態變數 > 父類static程式碼塊 > 子類static靜態變數 > 子類static程式碼塊 > 父類變數 > 父類例項程式碼塊 > 父類建構函式 > 子類變數 > 子類例項程式碼塊 > 子類建構函式
舉個例子:
public class StaticTest {
public static void main(String[] args) {
staticFunction();
}
private static int data;
static StaticTest st;
static { //靜態程式碼塊
System.out.println("1");
}
{ // 例項程式碼塊
System.out.println("2");
}
static {
data = 1;
}
static {
st = new StaticTest();
}
StaticTest() { // 例項構造器
System.out.println("3");
System.out.println("a=" + a + ",b=" + b);
}
public static void staticFunction() { // 靜態方法
System.out.println("4");
}
int a = 110; // 例項變數
static int b = 112; // 靜態變數
}
/**
* 輸出結果
* 1
* 2
* 3
* a=110,b=0
* 4
*/