Java的初始化可以分為兩個部分:
(a)類的初始化
(b)對象的創(chuàng)建
(a)類的初始化
**一辕坝、概念介紹: ** 一個類(class)要被使用必須經(jīng)過裝載堕油,連接螟蒸,初始化這樣的過程政溃。
- 在裝載階段,類裝載器會把編譯形成的class文件載入內(nèi)存,創(chuàng)建類相關(guān)的Class對象翼抠,這個Class對象封裝了我們要使用的類的類型信息咙轩。
- 連接階段又可以分為三個子步驟:驗(yàn)證、準(zhǔn)備和解析阴颖。
- 驗(yàn)證就是要確保java類型數(shù)據(jù)格式 的正確性活喊,并適于JVM使用。
- 準(zhǔn)備階段量愧,JVM為靜態(tài)變量分配內(nèi)存空間钾菊,并設(shè)置默認(rèn)值,注意偎肃,這里是設(shè)置默認(rèn)值煞烫,比如說int型的變量會被賦予默認(rèn)值0 。在這個階段累颂,JVM可能還會為一些數(shù)據(jù)結(jié)構(gòu)分配內(nèi)存滞详,目的是提高運(yùn)行程序的性能,比如說方法表喘落。
- 解析過程就是在類型的常量池中尋找類茵宪、接口最冰、字段和方法的符號引用瘦棋,把這些符號引用替換成直接引用。這個階段可以被推遲到初始化之后暖哨,當(dāng)程序運(yùn)行的過程中真正使用某個符號引用的時候 再去解析它赌朋。
- 類的初始化:
- 初始化類,是指初始化static靜態(tài)變量和執(zhí)行static靜態(tài)代碼塊篇裁。
- 初始化接口沛慢,是指初始化定義在該接口中的filed。
二达布、類的初始化條件:
類會在首次被“主動使用”時執(zhí)行初始化团甲,以下情況第一次發(fā)生之前類會被初始化:
- 創(chuàng)建類的實(shí)例;
- 調(diào)用類的靜態(tài)方法黍聂;
- 調(diào)用類的靜態(tài)變量躺苦,并且該變量不是一個常變量;
- 為類的靜態(tài)字段賦值产还;
- 在頂層類中執(zhí)行assert語句匹厘;(?)
- 調(diào)用類Class及包java.lang.reflect中的某些反射方法脐区;
JLS嚴(yán)格的說明:在任何其他的情況下愈诚,都不會對類或接口進(jìn)行初始化。
三、類的初始化規(guī)則:
- 類初始化時炕柔,該類的父類將首先被初始化酌泰,此過程一直遞歸到 java.lang.Object為止。但是父類實(shí)現(xiàn)的接口并不會被初始化匕累。
class Parent implements J{
{ System.out.println("父類初始化塊"); }
static{
System.out.println("父類靜態(tài)初始化塊");
}
}
class Child extends Parent implements I{
{ System.out.println("子類初始化塊"); }
static{
System.out.println("子類靜態(tài)初始化塊");
}
}
interface I {
int i = Test.out("interface : i", 1);
}
interface J {
int j = Test.out("interface : j", 2);
}
public class Test {
static int out(String s, int i) {
System.out.println(s + "=" + i);
return i;
}
public static void main(String [] args){
new Child();
}
}
接口只有被用到時才會被初始化
輸出:
父類靜態(tài)初始化塊
子類靜態(tài)初始化塊
父類初始化塊
子類初始化塊
- 接口初始化時宫莱,只會初始化該接口本身,并不會初始化它的父接口哩罪。
interface I {
int i = Test.out("interface : i", 1);
int ii = Test.out("interface : ii", 11);
}
interface J extends I{
int j = Test.out("interface : j", 2);
int jj = Test.out("interface : jj", 22);
}
public class Test {
static int out(String s, int i) {
System.out.println(s + "=" + i);
return i;
}
public static void main(String [] args){
System.out.println(J.j);
}
}
輸出:
interface : j=2
interface : jj=22
2
- 如果類的初始化是由于訪問靜態(tài)域而觸發(fā)授霸,那么只有真正定義該靜態(tài)域的類才被初始化,而不會觸發(fā)超類的初始化或者子類的初始化即使靜態(tài)域被子類或子接口或者它的實(shí)現(xiàn)類所引用际插。
示例1:(如上所述)
class Parent{
static int p = 10;
static{
System.out.println("父類靜態(tài)初始化塊");
}
}
class Child extends Parent{
static int c = 20;
static{
System.out.println("子類靜態(tài)初始化塊");
}
}
public class Test {
public static void main(String [] args){
System.out.println(Child.p); //靜態(tài)域p被子類引用
}
}
父類靜態(tài)初始化塊
10
示例2:(滿足類的初始化條件碘耳,父類也會被初始化,與示例1不同)
public class Test {
public static void main(String [] args){
System.out.println(Child.c);
}
}
父類靜態(tài)初始化塊
子類靜態(tài)初始化塊
20
- 如果一個靜態(tài)變量是編譯時常量框弛,則對它的引用不會引起定義它的類的初始化辛辨。
示例1:
class Parent{
static{
System.out.println("父類靜態(tài)初始化塊");
}
}
class Child extends Parent{
static final int x = 2005;
static{
System.out.println("子類靜態(tài)初始化塊");
}
}
public class Test {
public static void main(String [] args){
System.out.println(Child.x);
}
}
輸出:
2005
示例2:(I.i是一個編譯時常量,因此它不會引起I被初始化。)
interface I {
int i = 1;
int ii = Test.out("ii", 2);
}
public class Test {
static int out(String s, int i) {
System.out.println(s + "=" + i);
return i;
}
public static void main(String [] args){
System.out.println(I.i);
}
}
1
- 初始化類是指初始化static靜態(tài)變量和執(zhí)行static靜態(tài)代碼塊瑟枫,所以只會進(jìn)行一次斗搞。
初始化接口也只會進(jìn)行一次。
示例1:
class Child extends Parent{
static int c = 20;
static{
System.out.println("子類靜態(tài)初始化塊");
}
}
public class Test {
public static void main(String [] args){
System.out.println(Child.c);
System.out.println(Child.c);
}
}
父類靜態(tài)初始化塊
子類靜態(tài)初始化塊
20
20
示例2:
interface J{
int j = Test.out("j", 3);
int jj = Test.out("jj", 4);
}
public class Test {
static int out(String s, int i) {
System.out.println(s + "=" + i);
return i;
}
public static void main(String [] args){
System.out.println(J.j);
System.out.println(J.j);
}
}
j=3
jj=4
3
3
(b)對象創(chuàng)建過程中的Java初始化
一. 對象的創(chuàng)建過程總結(jié)
假設(shè)有個名為Dog的類:
- 當(dāng)首次創(chuàng)建類型為Dog的對象時慷妙,或者Dog類的靜態(tài)方法/靜態(tài)域首次被訪問時僻焚,java解釋器必須查找類路徑,以定位Dog.class文件膝擂。
- 然后載入Dog.class(這將創(chuàng)建一個Class對象)虑啤,有關(guān)靜態(tài)初始化的所有動作都會執(zhí)行。因此架馋,靜態(tài)初始化只在class對象首次加載的時候進(jìn)行一次狞山。
- 當(dāng)用new Dog()創(chuàng)建對象的時候,首先將在堆上為Dog對象分配足夠的存儲空間叉寂。
- 這塊存儲空間會被清零萍启,這就自動地將Dog對象中的所有基本類型數(shù)據(jù)都設(shè)置成了默認(rèn)值(對數(shù)字來說就是0,對布爾型與字符型也相同)屏鳍,而引用則被設(shè)置成了null勘纯。
- 執(zhí)行所有出現(xiàn)于字段定義處的初始化動作。
- 執(zhí)行構(gòu)造器孕蝉。
---《Thinking in java》
二. 對象創(chuàng)建過程中初始化順序
父靜態(tài)成員>子靜態(tài)成員>父普通成員初始化>父構(gòu)造>子普通成員初始化>子構(gòu)造.
( 靜態(tài)初始化塊以靜態(tài)變量對待)
- 示例1:
public class Test {
public static void main(String [] args){
new Child();
}
}
class Parent{
{
System.out.println("父類普通成員初始化塊");
}
static{
System.out.println("父類靜態(tài)成員及初始化塊");
}
public Parent(){
System.out.println("父類構(gòu)造函數(shù)");
}
}
class Child extends Parent{
{
System.out.println("子類普通成員初始化塊");
}
static{
System.out.println("子類靜態(tài)成員及初始化塊");
}
public Child(){
super();
System.out.println("子類構(gòu)造函數(shù)");
}
}
父類靜態(tài)成員及初始化塊
子類靜態(tài)成員及初始化塊
父類普通成員初始化塊
父類構(gòu)造函數(shù)
子類普通成員初始化塊
子類構(gòu)造函數(shù)
- 示例2:(證明 父構(gòu)造>子普通成員初始化>子構(gòu)造)
public class Test {
public static void main(String [] args){
new Child();
}
}
class Parent{
public Parent(){
System.out.println("父類構(gòu)造函數(shù)");
System.out.println("子類成員變量 height:" + ((Child)this).height);
}
}
class Child extends Parent{
public int height= 20;
{
System.out.println("子類非靜態(tài)成員初始化塊");
System.out.println("子類成員變量 height:" + this.height);
}
public Child(){
super();
System.out.println("子類構(gòu)造函數(shù)");
}
}
父類構(gòu)造函數(shù)
子類成員變量 height:0
子類非靜態(tài)成員初始化塊
子類成員變量 height:20
子類構(gòu)造函數(shù)
三. 對象創(chuàng)建過程的說明
- 靜態(tài)域的初始化是在類的初始化期間屡律,非靜態(tài)域的初始化時在類的實(shí)例創(chuàng)建期間。這意味這靜態(tài)域初始化在非靜態(tài)域之前降淮。
- 非靜態(tài)域通過構(gòu)造器初始化超埋,子類在做任何初始化之前構(gòu)造器會隱含地調(diào)用父類的構(gòu)造器搏讶,這保證了父類非靜態(tài)實(shí)例變量初始化早于子類。
- 調(diào)用Class的類成員變量時霍殴,構(gòu)造函數(shù)和成員變量不會執(zhí)行
public class Test {
public static void main(String [] args){
System.out.println(Parent.a);
}
}
class Parent{
public static int a = 10;
{
System.out.println("父類普通成員初始化塊");
}
public Parent(){
System.out.println("父類構(gòu)造函數(shù)");
}
}
輸出:10
- 在類的內(nèi)部媒惕,變量定義的先后順序決定了初始化的順序;
即使變量定義散布于方法定義之間,它們?nèi)詴谌魏畏椒ǎò?gòu)造器)被調(diào)用之前得到初始化来庭。
public class Test {
public static void main(String [] args){
new Parent();
}
}
class Parent{
{
System.out.println("普通成員初始化塊1");
}
public Parent(){
System.out.println("構(gòu)造函數(shù)");
}
{
System.out.println("普通成員初始化塊2");
}
}
普通成員初始化塊1
普通成員初始化塊2
構(gòu)造函數(shù)
- 多態(tài)情況下的對象初始化
public class Test {
public static void main(String [] args){
Parent parent = new Child();
}
}
class Parent{
{
System.out.println("父類普通成員初始化塊");
}
static{
System.out.println("父類靜態(tài)成員及初始化塊");
}
public Parent(){
System.out.println("父類構(gòu)造函數(shù)");
}
}
class Child extends Parent{
{
System.out.println("子類普通成員初始化塊");
}
static{
System.out.println("子類靜態(tài)成員及初始化塊");
}
public Child(){
super();
System.out.println("子類構(gòu)造函數(shù)");
}
}
父類靜態(tài)成員及初始化塊
子類靜態(tài)成員及初始化塊
父類普通成員初始化塊
父類構(gòu)造函數(shù)
子類普通成員初始化塊
子類構(gòu)造函數(shù)
可見多態(tài)情況下的初始化與示例1中繼承情況下的初始化是一樣的妒蔚,因?yàn)槎际莿?chuàng)建的子類的實(shí)例。
- 程序的執(zhí)行過程月弛,即Java虛擬機(jī)執(zhí)行Test 的靜態(tài)方法main肴盏,這也會引起Test 類的初始化。(理解面向?qū)ο蠖敲嫦蜻^程)
public class Test {
public static void main(String [] args){
System.out.println("Test- main");
}
static{
System.out.println("靜態(tài)成員及初始化塊");
}
}
靜態(tài)成員及初始化塊
Test- main
2015/8