深入介紹Java對象初始化
前言
在Java中,一個對象在可以被使用之前必須要被正確地初始化,這一點是Java規(guī)范規(guī)定的。
自動初始化(默認值)
一個類的所有基本數(shù)據(jù)成員都會得到初始化,運行下面的例子可以查看這些默認值:
class Default{ boolean t; char c; byte b; short s; int i; long l; float f; double d; public void show() { System.out.println("基本類型 初始化值\n"+ "boolean<----->" + t +"\n" + "char<----->" + c +"\n" + "byte<----->" + b + "\n" + "short<----->" + s + "\n" + "int<----->" + i + "\n" + "long<----->" + l + "\n" + "float<----->" + f + "\n" + "double<----->" + d + "\n" ); } } public class InitValue { public static void main(String[] args) { Default d = new Default(); d.show(); } }
【運行結果】:
基本類型 初始化值
boolean<----->false
char<----->
byte<----->0
short<----->0
int<----->0
long<----->0
float<----->0.0
double<----->0.0
其中,char類型的默認值為空(null)。
對于非基本數(shù)據(jù)類型而言,對象的句柄也會被初始化:
class Person { private String name; // setter } class Default { Person p; public void show() { System.out.println("Person<----->" + p); } } public class InitValue { public static void main(String[] args) { Default d = new Default(); d.show(); } }
【運行結果】:
Person<----->null
可見,句柄初始化值為null。這就是說,如果沒有為p指定初始化值就調(diào)用類似于p.setName
的方法,就會出現(xiàn)異常。
規(guī)定初始化
如果需要自己為變量賦一個初始值,可以在定義變量的同時賦值。
class Default{ boolean t = true; char c = 'A'; byte b = 47; short s = 0xff; int i = 24; long l = 999; float f = 1.2f; double d = 1.732; public void show() { System.out.println( "boolean<----->" + t +"\n" + "char<----->" + c +"\n" + "byte<----->" + b + "\n" + "short<----->" + s + "\n" + "int<----->" + i + "\n" + "long<----->" + l + "\n" + "float<----->" + f + "\n" + "double<----->" + d + "\n" ); } } public class InitValue { public static void main(String[] args) { Default d = new Default(); d.show(); } }
甚至可以通過一個方法來進行初始化;
class Person { int i = set(); //... }
這些方法也可以使用自變量:
class Person { int i; int j = set(i); //... }
構建器初始化
構建器進行初始化的優(yōu)點是可以在運行期決定初始化值。例如:
class Person { int age; Person() { age = 89; } }
age首先會初始化為0,然后變成89。對于所有基本類型以及對象的句柄,這種情況都是成立的。
初始化順序
在一個類里,初始化的順序是由變量在類內(nèi)的定義順序決定的。即使變量定義大量遍布于方法定義的中間,那么變量仍然會在調(diào)用任何方法(包括構造函數(shù))之前得到初始化。例如:
class Pet { Pet(int age) { System.out.println("Pet(" + age + ")"); } } class Person { Pet t1 = new Pet(1); Person() { System.out.println("---Person()---"); t3 = new Pet(33); } Pet t2 = new Pet(2); void show() { System.out.println("show----running"); } Pet t3 = new Pet(3); } public class OrderOfInitialization { public static void main(String[] args) { Person p = new Person(); p.show(); } }
【運行結果】:
Pet(1)
Pet(2)
Pet(3)
---Person()---
Pet(33)<br/>
show----running
上例中,雖然t1、t2、t3的定義遍布于類中,但是初始化的先后順序是由t1、t2、t3的定義順序決定的(自己動手調(diào)換t1、t2、t3看看結果),且初始化優(yōu)先于構建器執(zhí)行,當調(diào)用Person的構建器時,t3重新初始化。
靜態(tài)數(shù)據(jù)的初始化
如果數(shù)據(jù)是靜態(tài)的(static),同樣的過程也會執(zhí)行。若屬于基本類型,而且未對其進行初始化,就會自動獲得自己的標準基本類型初始值;若它是指向一個對象的句柄,除非創(chuàng)建一個對象同它連接起來,否則得到一個空值(null)。如果在定義時初始化,采取的方式與非靜態(tài)值是不同的,這是因為static只有一個存儲區(qū)域。例如:
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)
Bowl(2)
Table()
f(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f(2)
f2(1)
f3(1)
靜態(tài)代碼塊
Java允許將其他static初始化工作劃分到類內(nèi)一個特殊的代碼塊中,這種代碼塊的形式為static關鍵字,后面跟著一個方法主體,稱為靜態(tài)代碼塊。靜態(tài)代碼塊只有在第一次生成那個類的對象或首次訪問屬于那個類的static成員時執(zhí)行。例如:
class Person { Person(int age) { System.out.println("Person(" + age + ")"); } void f(int age) { System.out.println("f(" + age + ")"); } } class Persons { static Person p1; static Person p2; static { p1 = new Person(1); p2 = new Person(2); } Persons() { System.out.println("Persons()"); } } public class ExplicitStatic { public static void main(String[] args) { System.out.println("Inside main()"); Persons.p1.f(18);//1 } static Persons x = new Persons();//2 static Persons y = new Persons();//2 }
在標記為1的行內(nèi)訪問static對象p1的時候,或在行1被注釋而行2未被注釋是,用于Persons的static初始化模塊就會運行。若1和2都被注釋掉,則用于Persons的靜態(tài)代碼塊不會執(zhí)行。
靜態(tài)屬性和靜態(tài)代碼塊執(zhí)行的先后順序
class Person { Person(int age) { System.out.println("Person("+age+")"); } } class Persons { static Person p = new Person(2); // 1 static { p = new Person(3); } static Person p = new Person(2); // 2 } public class CompStaticInit { public static void main(String[] args) { } static Persons x = new Persons(); }
根據(jù)注釋1保留2,注釋2保留1的結果分析可知,靜態(tài)屬性和靜態(tài)代碼塊的執(zhí)行順序取決于編碼的順序。誰在前面就先執(zhí)行誰。
非靜態(tài)屬性的初始化
class Animal { Animal(int age) { System.out.println("Animal(" + age + ")"); } void f(int age) { System.out.println("f(" + age + ")"); } } public class NotStaticInit { Animal a1; Animal a2; { a1 = new Animal(1); a2 = new Animal(2); System.out.println("a1 & a2 initialized"); } NotStaticInit() { System.out.println("NotStaticInit"); } public static void main(String[] args) { System.out.println("Inside main()"); NotStaticInit x = new NotStaticInit(); } }
類似于靜態(tài)代碼塊,匿名代碼塊與非靜態(tài)屬性的初始化順序取決于編碼順序。
繼承中的對象初始化過程
class Insect { int i = 1; int j; Insect() { prt("i = " + i + ", j = " + j); j = 2; } static int x1 = prt("static Insect.x1 initialized"); static int prt(String s) { System.out.println(s); return 3; } } public class Beetle extends Insect { int k = prt("Beeklt.k initialized"); Beetle() { prt("k = " + k); prt("j = " + j); } static int x2 = prt("static Bootle.x2 initialized"); static int prt(String s) { System.out.println(s); return 4; } public static void main(String[] args) { prt("Beetle constructor"); Beetle b = new Beetle(); } }
【運行結果】:
static Insect.x1 initialized
static Bootle.x2 initialized
Beetle constructor
i = 1, j = 0
Beeklt.k initialized
k = 4
j = 2
對Beetle運行Java時,發(fā)生的第一件事情是裝載程序到外面找到那個類。在裝載過程中,裝載程序發(fā)現(xiàn)一個基礎類,所以隨之將其載入。無論是否生成基礎類的對象,這一過程都將執(zhí)行。如果基礎類含有另一個基礎類,則另一個基礎類隨即也會載入,以此類推。接下來就在根基礎類中執(zhí)行static初始化,再在下一個衍生類中執(zhí)行,以此類推。這是因為衍生類的初始化可能要依賴于對基礎類成員的初始化。
當類都裝載完畢,就能創(chuàng)建對象。首先,這個對象中的所有基本數(shù)據(jù)類型都會設置成為他們的默認值,對象句柄設為null。然后執(zhí)行基礎類的構建器。這種情況是自動完成的(衍生類的構造函數(shù)中默認調(diào)用了super(),
也可以通過super指定基類的構建器)?;A類構建器完成后,衍生類實例變量就會按本來的順序得到初始化,然后執(zhí)行構建器的剩余的主體部分。
總結對象創(chuàng)建的過程:
靜態(tài)只在類加載的時候執(zhí)行且只執(zhí)行一次;
非靜態(tài)只有在實例化的時候執(zhí)行,每次創(chuàng)建對象都執(zhí)行;
靜態(tài)在非靜態(tài)之前執(zhí)行,基類靜態(tài)優(yōu)先于衍生類靜態(tài)執(zhí)行;
靜態(tài)屬性和靜態(tài)代碼塊的執(zhí)行屬性取決于它們在類中的位置,誰在前先執(zhí)行誰;
非靜態(tài)屬性和構造塊的執(zhí)行順序取決于它們在類中的位置,誰在前執(zhí)行誰。
總結
通過上面的介紹,我們對Java中初始化對象的幾種方式以及通過何種方式執(zhí)行初始化代碼有了了解,同時也對何種情況下我們可能會使用到未經(jīng)初始化的變量進行了介紹。在對這些問題有了詳細的了解之后,就可以在編碼中規(guī)避一些風險,保證一個對象在可見之前是完全被初始化的。
相關文章
SpringBoot2.x設置Session失效時間及失效跳轉方式
這篇文章主要介紹了SpringBoot2.x設置Session失效時間及失效跳轉方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03關于springboot中對sqlSessionFactoryBean的自定義
這篇文章主要介紹了springboot中對sqlSessionFactoryBean的自定義方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12RabbitMQ 的消息持久化與 Spring AMQP 的實現(xiàn)詳解
這篇文章主要介紹了RabbitMQ 的消息持久化與 Spring AMQP 的實現(xiàn)剖析詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-08-08Java并發(fā)編程之ReentrantLock實現(xiàn)原理及源碼剖析
ReentrantLock 是常用的鎖,相對于Synchronized ,lock鎖更人性化,閱讀性更強,文中將會詳細的說明,請君往下閱讀2021-09-09