天天看點

java程式設計思想——多态1. Java中的多态性了解2.向上轉型和向下轉型參考資料:

文章目錄

  • 1. Java中的多态性了解
    • java程式設計思想——Java中的動态、靜态綁定(前期、後期綁定)
  • 2.向上轉型和向下轉型
    • 一.向上轉型
    • 二.向下轉型
  • 參考資料:

1. Java中的多态性了解

Java中除了static方法和final方法(private方法本質上屬于final方法,因為不能被子類通路)之外,其它所有的方法都是動态綁定,這意味着通常情況下,我們不必判定是否應該進行動态綁定—它會自動發生。

java程式設計思想——Java中的動态、靜态綁定(前期、後期綁定)

  • final方法會使編譯器生成更有效的代碼,這也是為什麼說聲明為final方法能在一定程度上提高性能(效果不明顯)。
  • 如果某個方法是靜态的,它的行為就不具有多态性:
class StaticSuper {
    public static String staticGet() {
        return "Base staticGet()";
    }

    public String dynamicGet() {
        return "Base dynamicGet()";
    }
}

class StaticSub extends StaticSuper {
    public static String staticGet() {
        return "Derived staticGet()";
    }

    public String dynamicGet() {
        return "Derived dynamicGet()";
    }
}

public class StaticPolymorphism {

    public static void main(String[] args) {
        StaticSuper sup = new StaticSub();
        System.out.println(sup.staticGet());
        System.out.println(sup.dynamicGet());
    }

}

輸出:
Base staticGet()
Derived dynamicGet()
           
  • 構造函數并不具有多态性,它們實際上是static方法,隻不過該static聲明是隐式的。是以,構造函數不能夠被override。
  • 在父類構造函數内部調用具有多态行為的函數将導緻無法預測的結果,因為此時子類對象還沒初始化,此時調用子類方法不會得到我們想要的結果。
class A {
    void draw() { System.out.println("A.draw()"); }
    A() {
        System.out.println("A() before draw()");
        draw();
        System.out.println("A() after draw()");
    }
}

class B extends A {
    private int b = 1;
    B(int b) {
        this.b = b;
        System.out.println("B(), b = " + this.b);
    }
    void draw() {
        System.out.println("B.draw(), b = " + this.b);
    }
}

public class Test {
    public static void main(String[] args) {
        new B(5);
    }
}

           

本來以為會輸出:

A() before draw()
A.draw()
A() after draw()
B(), b = 5
           

其實真正的輸出為:

A() before draw()
B.draw(), b = 0
A() after draw()
B(), b = 5
           

這是解釋:

1、父類構造器先于子類構造器被調用,是以會先輸出A() before draw();

2、調用run()時,雖然子類對象還沒生成,但是這裡的draw()方法仍然是動态方法,即後期綁定 ,是以調用的還是子類的draw()方法。另外由于建立對象時,是配置設定空間+初始化空間的,是以此時b的值為0,于是輸出:B.draw(), b=0; 除非這個b屬性是靜态屬性而且是聲明的時候就已經指派過了,那麼輸出的b就不是0了,而是初始賦的那個值。

3、然後接着運作父類構造器,輸出A() after draw();

4、最後運作子類構造器,此時子類的各屬性都已經被初始化,是以b被指派,輸出:B(), b=5

  • 隻有非private方法才可以被覆寫,但是還需要密切注意覆寫private方法的現象,這時雖然編譯器不會報錯,但是也不會按照我們所期望的來執行,即覆寫private方法對子類來說是一個新的方法而非重載方法。是以,在子類中,新方法名最好不要與基類的private方法采取同一名字(雖然沒關系,但容易誤解,以為能夠覆寫基類的private方法)。
  • Java類中屬性域的通路操作都由編譯器解析,是以不是多态的。父類和子類的同名屬性都會配置設定不同的存儲空間,如下:
// Direct field access is determined at compile time.
class Super {
    public int field = 0;
    public int getField() {
        return field;
    }
}

class Sub extends Super {
    public int field = 1;
    public int getField() {
        return field;
    }
    public int getSuperField() {
        return super.field;
    }
}

public class FieldAccess {

    public static void main(String[] args) {
        Super sup = new Sub();
        System.out.println("sup.filed = " + sup.field + 
                ", sup.getField() = " + sup.getField());
        Sub sub = new Sub();
        System.out.println("sub.filed = " + sub.field + 
                ", sub.getField() = " + sub.getField() + 
                ", sub.getSuperField() = " + sub.getSuperField());
    }

}

輸出:

sup.filed = 0, sup.getField() = 1
sub.filed = 1, sub.getField() = 1, sub.getSuperField() = 0
           

Sub子類實際上包含了兩個稱為field的域,然而在引用Sub中的field時所産生的預設域并非Super版本的field域,是以為了得到Super.field,必須顯式地指明super.field。

總而言之:注意多态隻是對方法調用起作用的,對域(成員變量)是不起作用的,即對域的調用是編譯器解析的,

類中域與靜态方法的調用是在編譯期進行解析的,不具有多态性。

2.向上轉型和向下轉型

一.向上轉型

package com.sheepmu;
 class Animal
 {
	public void eat()
	{
		System.out.println("父類的 eating...");
	}
}
class Bird extends Animal
{	
	@Override
	public void eat()
	{
		System.out.println("子類重寫的父類的  eatting...");
	}	
	public void fly()
	{
		System.out.println("子類新方法  flying...");
	}
}
public class Sys
{
	public static void main(String[] args) 
	{
		Animal b=new Bird(); //向上轉型
		b.eat(); 
		//  b.fly(); b雖指向子類對象,但此時子類作為向上的代價丢失和父類不同的fly()方法
		sleep(new Male());
		sleep(new Female());//傳入的參數是子類-----!!
	}
	
	public static void sleep(Human h) //方法的參數是父類------!!!
        {
 		 h.sleep();
        }
}                        

           
package com.sheepmu;
 
public class Human 
{
	public void sleep() 
	{
		System.out.println("父類人類   sleep..");
	}
}
class Male extends Human
{
	@Override
	public void sleep() 
	{
		System.out.println("男人 sleep..");
	}
}
class Female extends Human 
{
	@Override
	public void sleep()
	{
		System.out.println("女人 sleep..");
	}
}
                        
           

輸出:

子類重寫的父類的 eatting…

男人 sleep…

女人 sleep…

詳解:

  1. 向上轉型的實作
    Animal b=new Bird(); //向上轉型
        b.eat(); // 調用的是子類的eat()方法
        b.fly(); // 報錯!!!!!-------b雖指向子類對象,但此時子類作為向上轉型的代價丢失和父類不同的fly()方法------
               

2.為何不直接Bird b=new Bird();b.eat() 呢?

這樣就沒有展現出面向對象的抽象的程式設計思想呀,降低了代碼的可擴充性.

3.向上轉型的好處?

sleep(new Male());//調用方法時傳入的參數是子類
       sleep(new Female());

         public static void sleep(Human h) //方法的參數是父類

        {

         h.sleep();

         }
           

如上代碼就是用的向上轉型,若是不用向上轉型,那麼有多少個子類就得在這兒寫多少種不同的睡覺方法~~~~~~

二.向下轉型

package com.sheepmu;
 class Fruit
  {
	public void myName()
	{
		System.out.println("我是父類  水果...");
	}
}
 
class Apple extends Fruit
{ 
	@Override
	public void myName() 
	{ 
		System.out.println("我是子類  蘋果...");
	}
	public void myMore()
	{
		System.out.println("我是你的小呀小蘋果~~~~~~");
	}
}
 
public class Sys{ 
	public static void main(String[] args) { 
		Fruit a=new Apple(); //向上轉型
		a.myName();
		
		Apple aa=(Apple)a; //向下轉型,編譯和運作皆不會出錯(正确的)
		aa.myName();//向下轉型時調用的是子類的
		aa.myMore();;
		  
		Fruit f=new Fruit();
        Apple aaa=(Apple)f; //-不安全的---向下轉型,編譯無錯但會運作會出錯
  		aaa.myName();
  		aaa.myMore(); 
	}
}

           

輸出:

我是子類 蘋果…

我是子類 蘋果…

我是你的小呀小蘋果~~~~~~

Exception in thread “main” java.lang.ClassCastException: com.sheepmu.Fruit cannot be cast to com.sheepmu.Apple

at com.sheepmu.Sys.main(Sys.java:30)

詳解:

1.正确的向下轉型

Fruit a=new Apple(); //向上轉型

a.myName();

Apple aa=(Apple)a; //向下轉型,編譯和運作皆不會出錯(正确的)

aa.myName();

aa.myMore();

a指向子類的對象,是以子類的執行個體aa也可以指向a啊~~

向下轉型後因為都是指向子類對象,是以調用的當然全是子類的方法~~

2.不安全的向下轉型

Fruit f=new Fruit(); Apple aaa=(Apple)f; //-不安全的—向下轉型,編譯無錯但會運作會出錯

aaa.myName();

aaa.myMore();

f是父類對象,子類的執行個體aaa肯定不能指向父類f啊~~~

3.Java為了解決不安全的向下轉型問題,引入泛型的概念

4.為了安全的類型轉換,最好先用 if(A instanceof B) 判斷一下下~~

參考資料:

Java向上轉型和向下轉型(附詳細例子)