歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Java 構造函數內部的多態方法 完全剖析

Java 構造函數內部的多態方法 完全剖析

日期:2017/3/1 9:21:09   编辑:Linux編程

我們先來看一個例子,如果你讀過《Java編程思想》的話 應該會有印象

Java編程思想(第4版) 中文清晰PDF完整版 下載 http://www.linuxidc.com/Linux/2014-08/105403.htm

package com.test.zj;

public class PolyConstructors {

public static void main(String[] args) {
// TODO Auto-generated method stub
new RoundGlyph(5);
}

}

class RoundGlyph extends Glyph {
private int radius = 1;

public RoundGlyph(int r) {
// TODO Auto-generated constructor stub
radius = r;
System.out.println("RoundGlyph radius==" + radius);
}

@Override
void draw() {
// TODO Auto-generated method stub
System.out.println("RoundGlyph draw() radius==" + radius);
}

}

class Glyph {
void draw() {
System.out.println("print glyph.draw()");
}

Glyph() {
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw()");

}

}

對於java基礎一般的同學來說 這裡你可能會認為輸出是如下:

1 Glyph() before draw()
2 RoundGlyph draw() radius==1
3 Glyph() after draw()
4 RoundGlyph radius==5

但實際上你運行完畢以後 你會發現他的輸出是這樣的:

可能有的人讀到這裡還是不太明白我要表述什麼,那我再寫一個簡單的例子。先定義一個父類SuperClass

package com.test.zj;

public class SuperClass
{
private int superValue;

public SuperClass()
{
setSuperValue(100);

}

public void setSuperValue(int x)
{
superValue=x;
}

}

然後我們定義它的子類:

//這個子類繼承自父類superclass
public class SubClass extends SuperClass
{
private int subValue=10;

public SubClass()
{

}
//這個方法重寫了父類的方法
public void setSuperValue(int x)
{
//先調用父類的方法
super.setSuperValue(x);
//然後把值賦給自己的變量
subValue=x;

}

public void printSubValue()
{
System.out.println("subclass subvalue=="+subValue);
}

}

最後寫個main函數 就可以了

package com.test.zj;

public class MainClass {

public static void main(String[] args) {
// TODO Auto-generated method stub
SubClass sc=new SubClass();
sc.printSubValue();
}

}

好,現在我相信很多人都會認為第二個例子輸出的結果應該是100

但其實並沒有什麼卵用,他的實際結果是:

那到底這兩個例子都發生了什麼呢,我們直接來看字節碼好了,這個字節碼肯定不會有錯,字節碼怎麼寫的 jvm就怎麼執行。

我們就先看看第一個例子。

這裡應該很明顯的能看到 我們的main函數 一開始就是new了RoundGlyph這個對象。那我們看看這個類-c的結果吧

可以看到這個類的構造函數

先執行的是這個:

也就是說 先執行了glyph的構造方法 然後當glyph的構造函數執行完畢以後 才執行的賦值語句

我們的radius 作為一個int變量 在被執行之前 jvm自動初始化他的值為0!

所以你這裡隱隱約約應該都能猜到一個大概了,先執行的glyph的 構造函數,然後再給自己的成員變量radius賦值。

那我們看看glyph 都做了什麼吧:

你看glyph的構造函數, 在中間的時候13:invokevirtual #31 這裡,去執行了draw方法,但是子類我們重寫了這個draw方法

所以你看 在glyph的構造函數裡 調用子類的draw方法的時候 子類的radius賦值語句並沒有被執行到,所以子類的這個方法

輸出的值當然是0!

當父類glyph的構造函數執行完畢以後 ,我們的子類的賦值語句才終於得到執行。所以到這裡 你應該能明白第一個例子了。

那我們現在就可以去研究一下第二個例子,其實都是大同小異的。我們還是先看第二個例子的manclass和main函數

你看這裡main函數 先是new了一個subclass 子類的對象 對吧。那我們當然就要去看看subclass init方法

實際上這個地方就是Subclass的構造函數了。

這裡很清楚的可以看到 在subclass的構造函數裡 我們是先執行的superclass的構造函數,然後才給自己的subValue賦值為10.

那我們就去看看superclass裡都做了什麼。但實際上走到這裡我們已經能想到了無論你在superclass做了什麼 當你做完以後

subValue的值都必定為10.

所以當你subclass的對象構造完畢以後 此時他的成員變量subvalue的值就是10了,所以你當然打印出來這個變量的值 就一定是10了。

當然為了更清晰一點 我還是把superclass構造函數裡做了什麼稍微講一下,雖然這裡面做了什麼不會影響到我們的結論,但還是講一下吧,

即使這並沒有什麼卵用。。。

你看這裡就是調用了一下setSuperValue這個方法麼,對吧,因為子類重寫了這個方法 所以我們肯定要看看子類

這個方法干嘛的:

你看不就是又調用了父類的setSupervalue方法嗎,然後調用以後 你看有個iload putfield

這2個操作不就是給我們子類的subvalue 賦值的嗎,對吧。一直到這裡,我們子類的對象構造函數的第一步:

調用父類的構造函數 就算是走完了,走完了以後 才終於執行了自己的賦值語句:

好,這2個例子到這裡就算分析完畢了。

實際上最終的結論就是java編程思想裡說的那樣:

父類static成員 -> 子類static成員 -> 父類普通成員初始化和初始化塊 -> 父類構造方法 -> 子類普通成員初始化和初始化塊 -> 子類構造方法

如果你們有興趣的話,可以寫一個稍微更復雜一點的程序,驗證一下 上面的這個結論是否成立,廢話。。。。這結論肯定是成立的。但是

你如果用javap -c 這個命令 去看他們的字節碼的話 相信你能理解的更深了!

最後多說一句,平常我們在寫代碼的時候,盡量避免 上述2個例子這樣的寫法,因為這種情況造成的bug 很難被發現。。。即:

盡量不要在父類的構造函數裡 操作子類的成員變量。如果一定要把初始化寫的很麻煩的話,請考慮使用初始化塊 這樣一目了然的方法!

別問我為什麼會研究到這,因為tmd 有一個bug 找了好久 發現是這個原因啊!所以以後你們發現有人這麼寫,請直接寫郵件抄送全組投訴他啊!

編寫高質量代碼 改善Java程序的151個建議 PDF高清完整版 http://www.linuxidc.com/Linux/2014-06/103388.htm

Java 8簡明教程 http://www.linuxidc.com/Linux/2014-03/98754.htm

Java對象初始化順序的簡單驗證 http://www.linuxidc.com/Linux/2014-02/96220.htm

Java對象值傳遞和對象傳遞的總結 http://www.linuxidc.com/Linux/2012-12/76692.htm

Copyright © Linux教程網 All Rights Reserved