Java類載入與例項化過程
0x00 背景知識
1、虛擬機器在首次載入Java類時,會對靜態初始化塊、靜態成員變數、靜態方法(下文將這三種統稱為靜態程式碼塊)進行一次初始化
具體過程是:
①裝(加)載類的載入指的是將類的.class檔案中的二進位制資料讀入到記憶體中,將其放在執行時資料區的方法區中,然後在堆區建立一個java.lang.Class物件,用來封裝類在方法區內的資料結構,之後可以用Class物件進行相關的反射操作。
②連線分為三個子步驟
驗證:確保被載入的類的正確性 準備:為類的靜態變數分配記憶體,並將其初始化為預設值 解析: 把類中的符號引用轉換為直接引用
③初始化為為類的靜態變數賦予正確的初始值
2、類例項建立過程:按照父子繼承關係進行初始化,首先執行父類的初始化塊部分,然後是父類的構造方法;再執行本類繼承的子類的初始化塊,最後是子類的構造方法
3、類例項銷燬時候,首先銷燬子類部分,再銷燬父類部分
成員變數和靜態變數的區別:
1,成員變數所屬於物件。所以也稱為例項變數。
靜態變數所屬於類。所以也稱為類變數。
2,成員變數存在於堆記憶體中。
靜態變數存在於方法區中。
3,成員變數隨著物件建立而存在。隨著物件被回收而消失。
靜態變數隨著類的載入而存在。隨著類的消失而消失。
4,成員變數只能被物件所呼叫 。
靜態變數可以被物件呼叫,也可以被類名呼叫。
所以,成員變數可以稱為物件的特有資料,靜態變數稱為物件的共享資料。
靜態程式碼塊:就是一個有靜態關鍵字標示的一個程式碼塊區域。定義在類中。
作用:可以完成類的初始化。靜態程式碼塊隨著類的載入而執行,而且只執行一次(new 多個物件就只執行一次)。如果和主函式在同一類中,優先於主函式執行。(靜態成員就是靜態程式碼塊)
//靜態程式碼塊:在java中使用static關鍵字宣告的程式碼塊。靜態塊用於初始化類,為類的屬性初始化。每個靜態程式碼塊只會執行一次。由於JVM在載入類時會執行靜態程式碼塊,所以靜態程式碼塊先於主方法執行。
//如果類中包含多個靜態程式碼塊,那麼將按照"先定義的程式碼先執行,後定義的程式碼後執行"。
//注意:1 靜態程式碼塊不能存在於任何方法體內。2 靜態程式碼塊不能直接訪問靜態例項變數和例項方法,需要通過類的例項物件來訪問。
class Code{
{
System.out.println("Code的構造塊");
}
static{
System.out.println("Code的靜態程式碼塊");
}
public Code(){
System.out.println("Code的構造方法");
}
}
public class CodeBlock03{
{
System.out.println("CodeBlock03的構造塊");
}
static{
System.out.println("CodeBlock03的靜態程式碼塊");
}
public CodeBlock03(){
System.out.println("CodeBlock03的構造方法");
}
public static void main(String[] args){
System.out.println("CodeBlock03的主方法");
new Code();
new Code();
new CodeBlock03();
new CodeBlock03();
}
}
/*
CodeBlock03的靜態程式碼塊
CodeBlock03的主方法
Code的靜態程式碼塊
Code的構造塊
Code的構造方法
Code的構造塊
Code的構造方法
CodeBlock03的構造塊
CodeBlock03的構造方法
CodeBlock03的構造塊
CodeBlock03的構造方法
*/
構造程式碼塊:類中的獨立程式碼塊,與物件相關,物件呼叫一次就執行一次。
作用:給所有的物件進行初始化。
//構造塊:直接在類中定義且沒有加static關鍵字的程式碼塊稱為{}構造程式碼塊。構造程式碼塊在建立物件時被呼叫,每次建立物件都會被呼叫,並且構造程式碼塊的執行次序優先於類建構函式。
public class CodeBlock02{
{
System.out.println("第一程式碼塊");
}
public CodeBlock02(){
System.out.println("構造方法");
}
{
System.out.println("第二構造塊");
}
public static void main(String[] args){
new CodeBlock02();
new CodeBlock02();
new CodeBlock02();
}
}
/*
*
執行結果:
第一程式碼塊
第二構造塊
構造方法
第一程式碼塊
第二構造塊
構造方法
第一程式碼塊
第二構造塊
構造方法
*/
0x01先來幾個小測試:
測試一
class Singleton {
private static Singleton sin = new Singleton();
public static int counter1;
public static int counter2 = 0;
private Singleton() {
counter1++;
counter2++;
}
public static Singleton getInstance() {
return sin;
}
}
public class Test {
public static void main(String[] args) {
Singleton sin = Singleton.getInstance();
System.out.println(sin.counter1);
System.out.println(sin.counter2);
}
}
測試二:
class Singleton {
public static int counter1;
public static int counter2 = 0;
private static Singleton sin = new Singleton();
private Singleton() {
counter1++;
counter2++;
}
public static Singleton getInstance() {
return sin;
}
}
public class Test {
public static void main(String[] args) {
Singleton sin = Singleton.getInstance();
System.out.println(sin.counter1);
System.out.println(sin.counter2);
}
}
測試三:
class Singleton {
private static Singleton sin = new Singleton();
public static int counter1;
public static int counter2 = 0;
public Singleton() {
counter1++;
counter2++;
}
}
public class Test {
public static void main(String[] args) {
Singleton sin = new Singleton();
System.out.println(sin.counter1);
System.out.println(sin.counter2);
}
}
測試四:
class Singleton {
public static int counter1;
public static int counter2 = 0;
private static Singleton sin = new Singleton();
{
counter1=3;
}
public Singleton() {
counter1++;
counter2++;
}
}
public class Test {
public static void main(String[] args) {
Singleton sin = new Singleton();
System.out.println(sin.counter1);
System.out.println(sin.counter2);
}
}
結果:
測試一:1,0
測試二:1,1
測試三:2,1
測試四:4,2
0x02類載入
測試一分析:
按類載入的流程走一遍:
①裝(加)載類的載入指的是將類的.class檔案中的二進位制資料讀入到記憶體中,將其放在執行時資料區的方法區中,然後在堆區建立一個java.lang.Class物件,用來封裝類在方法區內的資料結構,之後可以用Class物件進行相關的反射操作。(都一樣)
②連線分為三個子步驟
驗證:確保被載入的類的正確性
準備:為類的靜態變數分配記憶體,並將其初始化為預設值
測試一中:sin=NULL;counter1=0;counter2=0;
解析: 把類中的符號引用轉換為直接引用
③初始化為為類的靜態變數賦予正確的初始值
測試一中第一句:
private static Singleton sin = new Singleton();//執行後counter1=1;counter2=2;
第二句:public static int counter1;//執行後counter1=1;
第二句:public static int counter2 = 0;//執行counter2=0;
所以最後結果為1,0
測試二同理。
0x03類的例項化
類例項建立過程簡單總結一下就是(new一個類的時候到底發生了什麼?):
預設初始化(對成員賦0或NULL)—>進建構函式—>super()【父類也是該過程】—>顯式初始化—>構造程式碼塊—>建構函式中其它程式碼
class Singleton {
/*
*靜態程式碼塊(靜態成員)
*/
private static Singleton sin = new Singleton();
public static int counter1;
public static int counter2 = 0;
/*
*構造程式碼塊
*/
{
counter1=3;
}
/*
*造函式
*/
public Singleton() {
//類Singleton 的super class為object
super();
//建構函式中其它程式碼
counter1++;
counter2++;
}
}
public class Test {
public static void main(String[] args) {
Singleton sin = new Singleton();
System.out.println(sin.counter1);
System.out.println(sin.counter2);
}
}
根據上面總結的流程走一遍:
類載入階段:跟測試一一樣,該階段結束後counter1=1;counter2=0;
類例項化階段:
預設初始化(對成員賦0或NULL)//沒有普通成員
—>進建構函式//開始進入Singleton()
—>super()【父類也是該過程】//進入super(),即Object載入
—>顯式初始化//super()執行完後開始顯示為成員賦值,本測試中沒有普通的非static成員
—>構造程式碼塊 //沒有
—>建構函式中其它程式碼// counter1++;counter2++;結束時counter1=2,counter2=1
測試四也走一遍:
類載入階段:跟測試一一樣,該階段結束後counter1=1;counter2=1;
類例項化階段:
預設初始化(對成員賦0或NULL)//沒有普通成員
—>進建構函式//開始進入Singleton()
—>super()【父類也是該過程】//進入super(),即Object載入
—>顯式初始化//super()執行完後開始顯示為成員賦值,本測試中沒有普通的非static成員
—>構造程式碼塊 //counter1=3;
—>建構函式中其它程式碼// counter1++;counter2++;結束時counter1=4,counter2=2
0x04 自測題
public class Farther
{
static
{
System.out.println("靜態程式碼塊 farther");
}
{
System.out.println("構造程式碼塊 farther");
}
public Farther()
{
System.out.println("call farther counter1="+Singleton.counter1+" counter2="+Singleton.counter2);
Singleton.counter1++;
}
}
class Singleton extends Farther {
static
{
System.out.println("靜態程式碼塊1 counter1="+Singleton.counter1 +" counter2="+Singleton.counter2);
}
public static int counter1;
public static int counter2 = 1;
static
{
System.out.println("靜態程式碼塊2 counter1="+counter1 +" counter2="+counter2);
}
private static Singleton sin = new Singleton();
static
{
System.out.println("靜態程式碼塊3 counter1="+counter1 +" counter2="+counter2);
}
{
counter1++;
System.out.println("構造程式碼塊 counter1="+counter1+" counter2="+counter2);
}
public Singleton() {
System.out.println("call Singleton");
counter1++;
counter2++;
}
}
public class Test {
public static void main(String[] args) {
//Singleton sin = Singleton.getInstance();
Singleton sin = new Singleton();
System.out.println("counter1="+sin.counter1);
System.out.println("counter2="+sin.counter2);
}
}
/*
靜態程式碼塊 farther
靜態程式碼塊1 counter1=0 counter2=0
靜態程式碼塊2 counter1=0 counter2=1
構造程式碼塊 farther
call farther counter1=0 counter2=1
構造程式碼塊 counter1=2 counter2=1
call Singleton
靜態程式碼塊3 counter1=3 counter2=2
構造程式碼塊 farther
call farther counter1=3 counter2=2
構造程式碼塊 counter1=5 counter2=2
call Singleton
counter1=6
counter2=3
*/
執行過程就是:
1.先載入 Farther
列印了:“靜態程式碼塊 farther”
2.後加載Singleton
先列印了:“靜態程式碼塊1 counter1=0 counter2=0“
”靜態程式碼塊2 counter1=0 counter2=1”
靜態程式碼塊: private static Singleton sin = new Singleton();呼叫了Singleton的建構函式,
先執行super(),所以“call farther counter1=0 counter2=1”被列印;
再執行構造程式碼塊,所以“構造程式碼塊 counter1=2 counter2=1”被列印
再執行建構函式中其他的程式碼,所以“call Singleton”被列印,建構函式呼叫結束。
接著執行靜態程式碼塊,所以“靜態程式碼塊3 counter1=3 counter2=2”被列印。
3.在main函式中呼叫Singleton sin = new Singleton();
預設初始化
—>進建構函式
—>super()【父類也是該過程,可看做該過程的一次遞迴】,//所以“構造程式碼塊 farther”和“call farther counter1=3 counter2=2”被列印
—>顯式初始化
—>構造程式碼塊 //所以“構造程式碼塊 counter1=5 counter2=2”被列印
—>建構函式中其它程式碼 //所以“call Singleton”被列印
最後列印:counter1=6
counter2=3