java中靜態變數,靜態程式碼塊,靜態方法,例項變數,匿名程式碼塊的載入順序
1. java中靜態變數,靜態程式碼塊,靜態方法,例項變數,匿名程式碼塊
在Java中,使用{}
括起來的程式碼稱為程式碼塊,程式碼塊可以分為以下四種:
(1)普通程式碼塊:就是類中方法的方法體
public void xxx(){
//code
}
(2)構造塊:用{}
裹起來的程式碼片段,構造塊在建立物件時會被呼叫,每次建立物件時都會被呼叫,並且優先於類建構函式執行。 構造塊中定義的變數是區域性變數。
{
//code
}
(3)靜態塊:用static{}
裹起來的程式碼片段,只會被執行一次(第一次載入此類時執行,比如說用Class.forName("")
static block
),靜態塊優先於構造塊執行。
static{
//code
}
(4)同步程式碼塊:使用synchronized(obj){}
裹起來的程式碼塊,在多執行緒環境下,對共享資料進行讀寫操作是需要互斥進行的,否則會導致資料的不一致性。常見的是synchronized用來修飾方法,其語義是任何執行緒進入synchronized需要先取得物件鎖如果被佔用了,則阻塞,實現了互斥訪問共享資源。而synchronized也是有代價的。一個常見的場景是,一個冗長的方法中,其實只有一小段程式碼需要訪問共享資源,這時使用同步塊,就只將這小段程式碼裹在synchronized block,既能夠實現同步訪問,也能夠減少同步引入的開銷。 同步程式碼塊須寫在方法中。
synchronized(obj){
//code
}
下面是一個例項:
public class Test { //1.第一步,準備載入類
public static void main(String[] args)
{
new Test(); //4.第四步,new一個類,但在new之前要處理匿名程式碼塊
// 這裡必須等待類載入完
System.out. println("done..");
Test.run();
}
static int num = 4; //2.第二步,靜態變數和靜態程式碼塊的載入順序由編寫先後決定
static
{
System.out.println("num:"+num); // 3.第三步,靜態塊,然後執行靜態程式碼塊,因為有輸出,故列印a
System.out.println("a");
}
{
num += 3;
System.out.println("b:"+num); //5.第五步,按照順序載入匿名程式碼塊,程式碼塊中有列印
}
int a = 5; //6.第六步,按照順序載入變數
{ // 成員變數第三個
System.out.println("c:"+a); //7.第七步,按照順序列印c
}
Test() { // 類的建構函式,第四個載入
System.out.println("d"); //8.第八步,最後載入建構函式,完成物件的建立
}
static void run() // 靜態方法,呼叫的時候才載入
{
System.out.println("e");
}
}
執行:
num:4
a
b:7
c:5
d
done..
e
一般順序:靜態塊(靜態變數)——>成員變數——>構造方法——>靜態方法
1、靜態程式碼塊(只加載一次)
2、構造方法(建立一個例項就載入一次)
3、靜態方法需要呼叫才會執行
繼承類的靜態變數,靜態程式碼塊,靜態方法,例項變數之間的執行順序:
例子1:
class Print {
public Print(String s){
System.out.print(s + " ");
}
}
class Parent{
public static Print obj1 = new Print("1");
public Print obj2 = new Print("2");
public static Print obj3 = new Print("3");
static{
new Print("4");
}
public static Print obj4 = new Print("5");
public Print obj5 = new Print("6");
public Parent(){
new Print("7");
}
}
class Child extends Parent{
static
{
//System.out.println(" problem...");
new Print("a");
}
public static Print obj1 = new Print("b");
public Print obj2 = new Print("c");
public Child (){
new Print("d");
}
public static Print obj3 = new Print("e");
public Print obj4 = new Print("f");
}
public class Test1 {
public static void main(String [] args){
Parent obj1 = new Child ();
Parent obj2 = new Child ();
}
}
執行:
1 3 4 5 a b e 2 6 7 c f d 2 6 7 c f d
輸出結果表明,程式的執行順序為:
如果類還沒有被載入:
1、先執行父類的靜態程式碼塊和靜態變數初始化,並且靜態程式碼塊和靜態變數的執行順序只跟程式碼中出現的順序有關。
2、執行子類的靜態程式碼塊和靜態變數初始化。
3、執行父類的例項變數初始化
4、執行父類的建構函式
5、執行子類的例項變數初始化
6、執行子類的建構函式
如果類已經被載入:
則靜態程式碼塊和靜態變數就不用重複執行,再建立類物件時,只執行與例項相關的變數初始化和構造方法。
例子2:
class H1{
{
System.out.println("父類程式碼塊");
}
public H1(){
System.out.println("父類構造");
}
static{
System.out.println("父類靜態程式碼塊");
}
}
class H2 extends H1{
static{
System.out.println("子類靜態程式碼塊");
}
{
System.out.println("子類程式碼塊");
}
public H2(){
System.out.println("子類構造");
}
}
public class Test1 {
public static void main(String [] args){
new H2();
}
}
執行:
父類靜態程式碼塊
子類靜態程式碼塊
父類程式碼塊
父類構造
子類程式碼塊
子類構造
執行流程分析:
1.java程式中靜態內容是隨著類的載入而載入的,由於存在繼承關係,因此先載入父類而後載入子類,相應的就是先執行父類靜態程式碼塊,再執行子類靜態程式碼塊
2.類載入完成後程式就開始執行main方法中,緊接著進行初始化工作,由於程式碼塊執行優於構造方法,因此出現先執行父類程式碼塊,再執行父類構造方法,緊接著子類程式碼塊,子類構造方法。
3.類的初始化是分層初始化的,先對父類進行初始化,再對子類進行初始化。在目標類中執行順序為:1.成員變數初始化:預設初始化----》顯示初始化----》構造方法初始化
2. 普通內部類和靜態內部類總結
- 普通內部類可以訪問其外部類的各種型別成員,但是靜態內部類只能訪問靜態成員
- 普通內部類裡面不能定義各種靜態的成員(包括靜態變數、靜態方法、靜態程式碼塊和靜態內部類),而靜態內部類中則可以;
(1)靜態變數和靜態方法會出現這個語法錯誤(static methods can only be declared in a static or top level type)意思就是static方法只能在靜態或者頂級型別(頂級型別應該就是外部類中)中宣告,當然static變數和static內部類也是一樣的道理。原因在靜態變數和靜態方法都只需要通過類名就能訪問,不必通過任何例項化物件;而普通內部類的初始化要利用外部類的例項化物件,這明顯違背了static的設計初衷。
(2)靜態程式碼塊會出現這個語法錯誤(Cannot define static initializer in inner type Outer.Inner)意思是不能在內部類中定義靜態的初始化程式。
原因跟以上的差不多,static宣告的成員只能為類所共有,而不能僅屬於一個例項化物件,通俗點來說就是不管有多少層的引用,都只能是類來引用而不能是物件。
3. 理解向上轉型:父類引用指向子類物件A a = New B()
向上轉型是JAVA中的一種呼叫方式,是多型的一種表現。向上轉型並非是將B自動向上轉型為A的物件,相反它是從另一種角度去理解向上兩字的:它是對A的物件的方法的擴充,即A的物件可訪問B從A中繼承來的和B複寫A的方法,其它的方法都不能訪問,包括A中的私有成員方法。
例子:
class Animal{
public void sleep(){
System.out.println("Animal sleep");
}
public void eat() {
System.out.println("Animal eat");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("dog eat meat");//重寫父類方法
}
//子類定義了自己的新方法
public void methods() {
System.out.println("dog method");
}
}
public class Demo {
public static void main(String[] args) {
Animal a = new Dog();
a.sleep();
a.eat();
//a.methods(); /*報錯:The method methods() is undefined for the type Animal*/
}
}
執行:
Animal sleep
dog eat meat
可以看出:
向上轉型後的a物件只能訪問從Animal中繼承來的和Dog複寫Animal的方法,其它的方法都不能訪問,包括Animal中的私有成員方法。但如果要訪問Dog類自己的方法,必須強制向下轉型 Dog c = (Dog)a;
。
另一個例子:
class Fu {
public int num = 100;
public void show() {
System.out.println("show Fu");
}
public static void function() {
System.out.println("function Fu");
}
}
class Zi extends Fu {
public int num = 1000;
public int num2 = 200;
public void show() {
System.out.println("show Zi");
}
public void method() {
System.out.println("method zi");
}
public static void function() {
System.out.println("function Zi");
}
}
public class DuoTaiDemo {
public static void main(String[] args) {
// 要有父類引用指向子類物件。
// 父 f = new 子();
Fu f = new Zi();
System.out.println(f.num);
// 找不到符號
// System.out.println(f.num2);
f.show();
// 找不到符號
// f.method();
f.function();
}
}
執行:
100
show Zi
function Fu
我們可以看到多型中的成員訪問特點:
- 成員變數
編譯看左邊,執行看左邊 - 構造方法
子類的構造都會預設訪問父類構造 - 成員方法
編譯看左邊,執行看右邊 - 靜態方法
編譯看左邊,執行看左邊
所以靜態方法不能算方法的重寫
參考:
https://blog.csdn.net/mrzhoug/article/details/51581994
https://blog.csdn.net/gh2391292/article/details/74421308