類載入,構造器初始化及static關鍵字
一.類載入與構造器初始化
1.類載入:Java命令的作用是啟動虛擬機器,虛擬機器通過輸入流,從磁碟上將位元組碼檔案(.class檔案)中的內容讀入虛擬機器,並儲存起來的過程就是類載入。
2.類載入特性: 在Java中,類是按需載入,只有當需要用到這個類的時候,才會載入這個類,並且在虛擬機器的生命週期中一個類只被載入一次。
3.類載入的原則:延遲載入,能少載入就少載入,因為虛擬機器的空間是有限的
4.從概念上講,初始化與建立是彼此獨立的,然而java中,初始化和建立是捆綁在一起的,兩者不能分離
5.對於方法內的區域性變數,聲明後不會自動初始化賦值,如果沒手動初始化,那麼使用該變數時,編譯器會報錯
對於類中的資料成員(也就是屬性),聲明後系統會自動初始化為該類資料型別的初始值
6.在生成物件的過程中,會先初始化物件的成員變數,然後再執行構造器。也就是說類中的變數會在任何方法(包括構造器)呼叫之前得到初始化,即使變數散步於方法定義之間。
7.類載入的步驟:
在Java中,類裝載器把一個類裝入Java虛擬機器中,要經過三個步驟來完成:裝載、連結和初始化,其中連結又可以分成校驗、準備和解析三步,除了解析外,其它步驟是嚴格按照順序完成的,各個步驟的主要工作如下:
- 裝載:查詢和匯入類或介面的二進位制資料
- 連結:執行下面的校驗、準備和解析步驟,其中解析步驟是可以選擇的
- 校驗:檢查匯入類或介面的二進位制資料的正確性
- 準備:給類的靜態變數分配並初始化儲存空間
- 解析:將符號引用轉成直接引用
- 初始化:啟用類的靜態變數的初始化Java程式碼和靜態Java程式碼塊
8.類載入的時機:
(1)第一次建立物件時要載入類(因為建立物件會呼叫構造方法,而構造方法也是static方法,只不過是隱式的)
(2)呼叫靜態方法時要載入類,訪問靜態屬性時會載入類
(3)載入子類時必定會先載入父類
(4)建立物件引用不載入類
(5)子類呼叫父類的靜態方法時
- 當子類沒有與父類同名的靜態方法時(這時是執行父類的方法,所以只需載入父類),只加載父類,不載入子類
- 當子類有與父類同名的靜態方法時(這是是執行子類的方法,需要載入子類,所以必須先載入父類),既載入父類,又載入子類
(6)訪問靜態常量,如果編譯器可以計算出常量的值,則不會載入類,例如:public static final int a =123;否則會載入類,例如:public static final int a = math.PI;
總的來說,類是在其任何static成員被訪問時載入的.初次使用之處也是static初始化之處,所有static物件和static程式碼段都會在載入時依程式中的順序(即定義類時的書寫順序)而依次初始化,當然定義為static的東西只會被初始化一次
9.物件的初始化順序:首先執行父類靜態的內容,父類靜態的內容執行完畢後,接著去執行子類的靜態的內容,當子類的靜態內容執行完畢之後,再去看父類有沒有非靜態程式碼塊,如果有就執行父類的非靜態程式碼塊,父類的非靜態程式碼塊執行完畢,接著執行父類的構造方法;父類的構造方法執行完畢之後,它接著去看子類有沒有非靜態程式碼塊,如果有就執行子類的非靜態程式碼塊。子類的非靜態程式碼塊執行完畢再去執行子類的構造方法。
總之一句話,靜態程式碼塊內容先執行,接著執行父類非靜態程式碼塊和構造方法,然後執行子類非靜態程式碼塊和構造方法。
注意:子類的構造方法,不管這個構造方法帶不帶引數,預設的它都會先去尋找父類的不帶引數的構造方法。如果父類沒有不帶引數的構造方法,那麼子類必須用supper關鍵子來呼叫父類帶引數的構造方法,否則編譯不能通過。
10.一個類中物件的定義一般都是分為以下兩步來進行的:
(1)A a; //定義了一個類A的引用
(2)a=new A(“10″,”2563”); //真正地建立了物件a,也就是a指向了記憶體中一塊連續的區域.
11.本地方法是一種在java中呼叫非java程式碼的方式
二.static關鍵字
static表示“全域性”或者“靜態”的意思,用來修飾成員變數和成員方法,也可以形成靜態static程式碼塊,但是Java語言中沒有全域性變數的概念。
被static修飾的成員變數和成員方法獨立於該類的任何物件。也就是說,它不依賴類特定的例項,被類的所有例項共享。只要這個類被載入,Java虛擬機器就能根據類名在執行時資料區的方法區內定找到他們。因此,static物件可以在它的任何物件建立之前訪問,無需引用任何物件。
用public修飾的static成員變數和成員方法本質是全域性變數和全域性方法,當宣告它類的物件時,不生成static變數的副本,而是類的所有例項共享同一個static變數。
static變數前可以有private修飾,表示這個變數可以在類的靜態程式碼塊中,或者類的其他靜態成員方法中使用(當然也可以在非靜態成員方法中使用),但是不能在其他類中通過類名來直接引用,這一點很重要。實際上只需要搞明白,private是訪問許可權限定,static表示不要例項化就可以使用,這樣就容易理解多了。static前面加上其它訪問許可權關鍵字的效果也以此類推。
static修飾的成員變數和成員方法習慣上稱為靜態變數和靜態方法,可以直接通過類名來訪問,訪問語法為:
- 類名.靜態方法名(引數列表…)
- 類名.靜態變數名
用static修飾的程式碼塊表示靜態程式碼塊,當Java虛擬機器(JVM)載入類時,就會執行該程式碼塊。
三.Static關鍵字的用途
1.Static變數
static變數也稱作靜態變數(也叫類變數),靜態變數和非靜態變數(例項變數)的區別是:靜態變數被所有的物件所共享,在記憶體中只有一個副本(因此在程式中任何物件對靜態變數做修改,其他物件看到的是修改後的值),它當且僅當在類初次載入時會被初始化。可用類名直接訪問(方便),當然也可以通過物件來訪問(但是這是不推薦的)。而非靜態變數是物件所擁有的,在建立物件的時候被初始化,存在多個副本,各個物件擁有的副本互不影響。
static成員變數的初始化順序按照定義的順序進行初始化。
注意:不能把任何方法體內的變數宣告為靜態(即static是不允許用來修飾區域性變數)
2.Static方法
靜態方法可以直接通過類名呼叫(任何的例項也可以呼叫),因此靜態方法中不能用this和super關鍵字,不能直接訪問所屬類的例項變數和例項方法(就是不帶static的成員變數和成員方法),只能訪問所屬類的靜態成員變數和靜態成員方法。因為例項成員和例項方法與特定的物件關聯.
因為static方法獨立於任何例項,因此static方法必須被實現,而不能是抽象的abstract。(即靜態方法不能是抽象的)
如果說想在不建立物件的情況下呼叫某個方法,就可以將這個方法設定為static。我們最常見的static方法就是main方法,至於為什麼main方法必須是static的,現在就很清楚了。因為程式在執行main方法的時候沒有建立任何物件,因此只有通過類名來訪問。
另外記住,即使沒有顯示地宣告為static,類的構造器實際上也是靜態方法。
3.Static程式碼塊
static程式碼塊也叫靜態程式碼塊,是在類中獨立於類成員的static語句塊,可以有多個,位置可以隨便放,它不在任何的方法體內,JVM載入類時會自動執行這些靜態的程式碼塊,如果static程式碼塊有多個,JVM將按照它們在類中出現的先後順序依次執行它們,靜態程式碼塊沒有名字,因此不能顯式呼叫,每個程式碼塊只會在類載入時被自動執行一次(因為在虛擬機器的生命週期中一個類只被載入一次;又因為static{}是伴隨類載入執行的,所以,不管你new多少次物件例項,static{}都只執行一次)。靜態塊常用來執行類屬性的初始化.
總結如下:
- 每個靜態程式碼塊只會在類載入時被執行一次
- 當一個類中有多個static{}的時候,按照static{}的定義順序,從前往後執行
- 先執行完static{}語句塊的內容,才會執行呼叫語句
- 靜態程式碼快中不能訪問非static成員(屬性和方法)
- 如果靜態變數在定義的時候就賦給了初值(如 static int X=100),那麼賦值操作也是在類載入的時候完成的,並且當一個類中既有static{}又有static變數的時候,同樣遵循“先定義先執行”的原則
例如:
class Test{
public static int X=300;
static{
System.out.println(X);
X=200;
System.out.println(X);
}
}
public class StaticBlockTest{
public static void main(String args[]){
System.out.println(Test.X);
}
}
結果:程式會依次輸出300,200,200,先執行完X=300,再執行static{}語句塊。
4.Static類
一個普通類是不能宣告為靜態的,只有一個內部類才可以。這時這個宣告為靜態的內部類可以直接作為一個普通類來使用,而不需例項化一個外部類。
例如:
public class StaticCls{
public static void main(String[] args){
OuterCls.InnerCls oi=new OuterCls.InnerCls();
}
}
class OuterCls{
public static class InnerCls{
InnerCls(){
System.out.println("InnerCls");
}
}
}
結果:程式輸出InnerCls
5.import static靜態匯入:詳情見博文《靜態匯入》
6.static和final一塊用表示什麼?
static final用來修飾成員變數和成員方法,可簡單理解為“全域性常量”
- 對於變數,表示一旦給值就不可修改,並且通過類名可以訪問。
- 對於方法,表示不可覆蓋,並且可以通過類名直接訪問。
注意:對於被static和final修飾過的例項常量,例項本身不能再改變了,但對於一些容器型別(比如,ArrayList、HashMap)的例項變數,不可以改變容器變數本身,但可以修改容器中存放的物件,這一點在程式設計中用到很多。
例如:
public class TestStaticFinal {
private static final String strStaticFinalVar = "aaa";
private static String strStaticVar = null;
private final String strFinalVar = null;
private static final int intStaticFinalVar = 0;
private static final Integer integerStaticFinalVar = new Integer(8);
private static final ArrayList<String> alStaticFinalVar = new ArrayList<String>();
private void test() {
System.out.println("-------------值處理前----------\r\n");
System.out.println("strStaticFinalVar=" + strStaticFinalVar + "\r\n");
System.out.println("strStaticVar=" + strStaticVar + "\r\n");
System.out.println("strFinalVar=" + strFinalVar + "\r\n");
System.out.println("intStaticFinalVar=" + intStaticFinalVar + "\r\n");
System.out.println("integerStaticFinalVar=" + integerStaticFinalVar + "\r\n");
System.out.println("alStaticFinalVar=" + alStaticFinalVar + "\r\n");
//strStaticFinalVar="哈哈哈哈"; //錯誤,final表示終態,不可以改變變數本身.
strStaticVar = "哈哈哈哈"; //正確,static表示類變數,值可以改變.
//strFinalVar="呵呵呵呵"; //錯誤, final表示終態,在定義的時候就要初值(哪怕給個null),一旦給定後就不可再更改。
//intStaticFinalVar=2; //錯誤, final表示終態,在定義的時候就要初值(哪怕給個null),一旦給定後就不可再更改。
//integerStaticFinalVar=new Integer(8); //錯誤, final表示終態,在定義的時候就要初值(哪怕給個null),一旦給定後就不可再更改。
alStaticFinalVar.add("aaa"); //正確,容器變數本身沒有變化,但存放內容發生了變化。這個規則是非常常用的,有很多用途。
alStaticFinalVar.add("bbb"); //正確,容器變數本身沒有變化,但存放內容發生了變化。這個規則是非常常用的,有很多用途。
System.out.println("-------------值處理後----------\r\n");
System.out.println("strStaticFinalVar=" + strStaticFinalVar + "\r\n");
System.out.println("strStaticVar=" + strStaticVar + "\r\n");
System.out.println("strFinalVar=" + strFinalVar + "\r\n");
System.out.println("intStaticFinalVar=" + intStaticFinalVar + "\r\n");
System.out.println("integerStaticFinalVar=" + integerStaticFinalVar + "\r\n");
System.out.println("alStaticFinalVar=" + alStaticFinalVar + "\r\n");
}
public static void main(String args[]) {
new TestStaticFinal().test();
}
}
四.分析幾道題目
1.下面這段程式碼的輸出結果是什麼?
public class Test extends Base{
static{
System.out.println("test static");
}
public Test(){
System.out.println("test constructor");
}
public static void main(String[] args) {
new Test();
}
}
class Base{
static{
System.out.println("base static");
}
public Base(){
System.out.println("base constructor");
}
}
輸出結果:
base static
test static
base constructor
Test constructor
結果分析:
先來想一下這段程式碼具體的執行過程,在執行開始,先要尋找到main方法,因為main方法是程式的入口,但是在執行main方法之前,必須先載入Test類,而在載入Test類的時候發現Test類繼承自Base類,因此會轉去先載入Base類,在載入Base類的時候,發現有static塊,便執行了static塊。在Base類載入完成之後,便繼續載入Test類,然後發現Test類中也有static塊,便執行static塊。在載入完所需的類之後,便開始執行main方法。在main方法中執行new Test()的時候會先呼叫父類的構造器,然後再呼叫自身的構造器。因此,便出現了上面的輸出結果。
2.這段程式碼的輸出結果是什麼?
public class Test {
Person person = new Person("Test");
static{
System.out.println("test static");
}
public Test() {
System.out.println("test constructor");
}
public static void main(String[] args) {
new MyClass();
}
}
class Person{
static{
System.out.println("person static");
}
public Person(String str) {
System.out.println("person "+str);
}
}
class MyClass extends Test {
Person person = new Person("MyClass");
static{
System.out.println("myclass static");
}
public MyClass() {
System.out.println("myclass constructor");
}
}
輸出結果:
test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor
結果分析:
類似地,我們還是來想一下這段程式碼的具體執行過程。首先載入Test類,因此會執行Test類中的static塊。接著執行new MyClass(),而MyClass類還沒有被載入,因此需要載入MyClass類。在載入MyClass類的時候,發現MyClass類繼承自Test類,但是由於Test類已經被載入了,所以只需要載入MyClass類,那麼就會執行MyClass類的中的static塊。在載入完之後,就通過構造器來生成物件。而在生成物件的時候,必須先初始化父類的成員變數,因此會執行Test中的Person person = new Person(),而Person類還沒有被載入過,因此會先載入Person類並執行Person類中的static塊,接著執行父類的構造器,完成了父類的初始化,然後就來初始化自身了,因此會接著執行MyClass中的Person person = new Person(),最後執行MyClass的構造器。
3.這段程式碼的輸出結果是什麼?
public class Test {
static{
System.out.println("test static 1");
}
public static void main(String[] args) {
}
static{
System.out.println("test static 2");
}
}
結果輸出:
test static 1
test static 2
結果分析:
雖然在main方法中沒有任何語句,但是還是會輸出,原因上面已經講述過了。另外,static塊可以出現類中的任何地方(只要不是方法內部,記住,任何方法內部都不行),並且執行是按照static塊的順序執行的。
4.這段程式碼的輸出結果是什麼?
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();
table.f2(1);
cupboard.f3(1);
}
static Table table = new Table();
static Cupboard cupboard = new Cupboard();
}
class Bowl{
Bowl(int marker){
System.out.println("Bowl("+marker+")");
}
void f1(int marker){
System.out.println("f1("+marker+")");
}
}
class Table{
static Bowl bowl1 = new Bowl(1);
Table(){
System.out.println("Table()");
}
void f2(int marker){
System.out.println("f2("+marker+")");
}
static Bowl bowl2 = new Bowl(2);
}
class Cupboard{
Bowl bowl3 = new Bowl(3);
static Bowl bowl4 = new Bowl(4);
Cupboard(){
System.out.println("Cupboard");
bowl4.f1(2);
}
void f3(int marker){
System.out.println("f("+marker+")");
}
static Bowl bowl5 = new Bowl(5);
}
輸出結果:
Bowl(1)
Bowl(2)
Table()
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f1(2)
f2(1)
f3(1)