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