實例一、
package Demo329; //2多態原理 class Parent{ String s = "s parent"; public Parent() { System.out.println("Parent 構造器"); System.out.println(this); this.fun(12); this.f(); this.p(); } void p(){ System.out.println("P"); } public static void f(){ System.out.println("Parent's static f()"); } private void fun(int i){ System.out.println("Parent's fun()"); } } class Child extends Parent{ String s = "s Child"; public Child(){ System.out.println("Child 構造器"); System.out.println(this); this.fun(12); this.f(); } public static void f(){ System.out.println("Child's static f()"); } public void fun(int i){ System.out.println("Child's fun()"); } } public class Demo{ public static void main(String[] args) { System.out.println("Parent t = new Child()------->"); Parent t = new Child(); System.out.println("/////////"+t.s); System.out.println("Parent p = new Parent()------>"); Parent p = new Parent(); System.out.println("Child c = new Child()--------> "); Child c = new Child(); System.out.println("///////////"+c.s); } }
/*OutPut:
Parent t = new Child()------->
Parent 構造器
Demo329.Child@15db9742
Parent's fun()
Parent's static f()
P
Child 構造器
Demo329.Child@15db9742
Child's fun()
Child's static f()
/////////s parent
Parent p = new Parent()------>
Parent 構造器
Demo329.Parent@6d06d69c
Parent's fun()
Parent's static f()
P
Child c = new Child()-------->
Parent 構造器
Demo329.Child@7852e922
Parent's fun()
Parent's static f()
P
Child 構造器
Demo329.Child@7852e922
Child's fun()
Child's static f()
///////////s Child
*/
*invokespecial 前期綁定,調用超類構造方法 實例初始化方法,私有方法
* invokevirtual 後期綁定調用實例方法 */
當程序開始編譯成.class文件時,發生了許多事情:
在編譯Parent的時候,開始編譯構造器,此時將這個構造方法被編譯成invokespecial類型。
接著開始編譯構造器裡面的語句:
當編譯System.out.println(this)時候,此時的this是不確定的所以是invokevirtual類型。
接著執行this.fun(12);對於這個this被解釋成invokespecial還是invokevirtual?
由於這個fun()函數在Parent中是static的,所以是靜態方法,只能在編譯期間進行綁定,所以是invokespecial 。
對於this.f()方法,因為這個方法是一個普通的方法,所以可能存在後期綁定的行為,所以this是一個invokevirtual 類型。
同樣的對於this.a()方法,雖然從代碼上看是Parent特有的方法,但是編譯器並不會知道,所以也會將這個this作為invokevirtual 對待。
然後就是編譯剩下的方法。同樣在Child中原理一樣。
然後在main方法中對於域的調用,不存在是否有後期綁定的行為,並且編譯器僅僅就只是能夠識別引用的類型,所以t.s為 s Parent。在類的構造器中也是如此,加入在Parent構造器中加入this.s 那麼編譯器就假定認為這個this就是“invokespecial”就是指當下的這個類。
實例二:
package Demo329; class SuperParent { SuperParent(){ System.out.println("SuperParent:---> "); this.f(); } void f(){ System.out.println("SuperParent"); } } class Parent extends SuperParent{ Parent(){ System.out.println("Parent:--->"); this.f(); super.f(); } void f(){ System.out.println("Parent"); } } class Child extends Parent{ public Child() { System.out.println("Child:--->"); this.f(); super.f(); } void f(){ System.out.println("Child"); } } public class Demo{ public static void main(String[] args) { Child c = new Child(); } }
/*Output:
SuperParent:--->
Child
Parent:--->
Child
SuperParent
Child:--->
Child
Parent
*/
編譯器編譯SuperParent時,遇到this.f()調用,此時f函數是一個一般的方法,所以y這個this應該是invokevirtual,所以應該是在後期綁定的時候由jvm判斷到底調用那個f方法。
接著編譯Parent方法,同理處理構造器中this.f( )。但是對於super來說,這個對於編譯器是確定的就是其基類,不會待運行的時候有什麼後期綁定,所以應該是invokespecial的。同理分析Child。
總之,編譯器是不會有判斷一個引用指向對象的類型的,它僅僅只是知道引用當下的類型,
同時對於this,與super到底指代的是誰,關鍵是要看調用額對象,如果對象是一個一般的方法,可能這個方法就是被重載後的,否則一般都是在編譯的時候就會綁定。
所以來說:
多態優點:消除了類型之間的耦合關系,增加了代碼的可擴展性。
多態缺點:對基類依賴性太高,更加偏向於前期綁定,對動態綁定機制享有的太少僅僅是子類中對父類重寫代碼部分。並且要用多態前提就是必須有繼承,還要有覆蓋操作。對設計類的時候有所限制。
目前多態情況下不能訪問子類特有的成員。解決這個缺點問題的方法就是:強制類型轉化
Eg:
類型轉換最場景的問題: java.lang.ClassCastException。 強制類型轉換失敗。
實例三:package Demo329; class print{ print(){ System.out.println("print調用了"); } } class SuperParent{ print t = new print(); public SuperParent() { System.out.println("我是SuperParent的構造器"); } { System.out.println("我是SuperParent的構造代碼塊"); } private static int x = printInit("我是SuperParent的靜態變量"); static { System.out.println("我是SuperParent的static代碼塊"); } public static int printInit(String s){ System.out.println(s); return 0; } } class Parent extends SuperParent{ public Parent() { System.out.println("我是Parent的構造器"); } private int x = printInit("我是Parent的成員變量"); { System.out.println("我是Parent的構造代碼塊"); } static { System.out.println("我是Parent的static代碼塊"); } print t = new print(); } class Child extends Parent{ public Child() { System.out.println("我是Child的構造器"); } { System.out.println("我是Child的構造代碼塊"); } static { System.out.println("我是Child的static代碼塊"); } private int x = printInit("我是Child的 成員 變量"); } public class Demo{ public static void main(String[] args) { Child c = new Child(); } }
/*OutPut:
我是SuperParent的靜態變量
我是SuperParent的static代碼塊
我是Parent的static代碼塊
我是Child的static代碼塊
print調用了
我是SuperParent的構造代碼塊
我是SuperParent的構造器
我是Parent的成員變量
我是Parent的構造代碼塊
print調用了
我是Parent的構造器
我是Child的構造代碼塊
我是Child的 成員 變量
我是Child的構造器
*/
對與一個類,在編譯的時候,編譯器會按照類中構造代碼塊和成員對象(即使是引用對象)的順序加載到構造器內語句的前面,其中如果內有初始化的會執行默認初始化,然後將static變量和static代碼塊按照順序 加載到一個static{}代碼塊中.
特別是執行默認初始化這一步是非常重要的,可以避免一些不必要的錯誤.比如:如果構造器只是在構造對象過程中的一個步驟,並且該對象所屬的類是從這個構造器所屬的類中導出的,那麼導出部分在當前構造器正在調用的時刻中是沒有初始化的。然後,一個動態綁定的方法調用卻會向外深入到繼承層次結構的內部,它可以調用導出類中的方法。但是可想而知,這個方法中所操縱的成員時還沒有初始化的,有可能出錯。並且很難排查。
對於多個類,當main函數中得知待用Child類,就會開始加在child但是又發現有基類接著加載基類以此類推,直到加載最後一個基類為止,此時從根基類開始現將所有的static成員按照從基類到導出類的順序執行完。這樣做的好處就是防止在導出類中使用基類中的static成員。接著按照調用基類的順序開始執行非靜態的內容比如構造器等。
實例四、多態的應用:
1.多態用於形參類型的時候,可以接收更多類型的數據 。
避免了因基類導出的多個子類在調用各自的某一方法時要多寫代碼,更能體現出繼承的關系,同時利用向上轉型可以使得多個子類在調用方法功能相同行為不同的方法出現冗余,同時再添加新類,也變得靈活。提高的可擴展性。
2. 多態用於返回值類型的時候,可以返回更多類型的數據。
//圖形類 abstract class MyShape{ public abstract void getArea(); public abstract void getLength(); } class Circle extends MyShape{ public static final double PI = 3.14; double r; public Circle(double r){ this.r =r ; } public void getArea(){ System.out.println("圓形的面積:"+ PI*r*r); } public void getLength(){ System.out.println("圓形的周長:"+ 2*PI*r); } } class Rect extends MyShape{ int width; int height; public Rect(int width , int height){ this.width = width; this.height = height; } public void getArea(){ System.out.println("矩形的面積:"+ width*height); } public void getLength(){ System.out.println("矩形的周長:"+ 2*(width+height)); } } class Demo12 { public static void main(String[] args) { /* //System.out.println("Hello World!"); Circle c = new Circle(4.0); print(c); Rect r = new Rect(3,4); print(r); */ MyShape m = getShape(0); //調用了使用多態的方法,定義的變量類型要與返回值類型一致。 m.getArea(); m.getLength(); } //需求1: 定義一個函數可以接收任意類型的圖形對象,並且打印圖形面積與周長。 public static void print(MyShape s){ // MyShpe s = new Circle(4.0); s.getArea(); s.getLength(); } // 需求2: 定義一個函數可以返回任意類型的圖形對象。 public static MyShape getShape(int i){ if (i==0){ return new Circle(4.0); }else{ return new Rect(3,4); } } }