1. 程式人生 > 其它 >多型和特殊類

多型和特殊類

一、多型

1、多型的概念

  • 多型主要指同一種事務表現出來的多種形態。

    飲料:可樂、雪碧、紅牛、脈動.....

    寵物:貓、狗、鳥、小強、魚......

    人:學生、教師、工人、保安.......

    圖形:矩形、圓形、梯形、三角形

2、多型的語法格式

  • 父類型別 引用變數名 = new 子類型別();

    如:Shape sr = new Rect();

    ​ sr.show();

3、多型的特點

  • 當父類型別的引用指向子類型別的物件時,父類型別的引用可以直接呼叫父類獨有的方法
  • 當父類型別的引用指向子類型別的物件時,父類型別的引用不可以直接呼叫子類獨有的方法
  • 對於父子類都有的非靜態方法來說,編譯階段呼叫父類版本,執行階段呼叫子類重寫的版本
  • 對於父子類都有的靜態方法來說,編譯和執行階段都呼叫父類版本

4、引用資料型別之間的轉換

  • 引用資料型別之間的轉換方式有兩種:自動型別轉換和強制型別轉換。
  • 自動型別轉換主要指小型別想大型別轉換,也就是子類向父類轉換,也叫做向上轉型。
  • 強制型別轉換主要指大型別向小型別的轉換,也就是父類轉為子類,也叫做向下轉型或者顯示轉型。
  • 引用資料型別之間的轉換必須發生在父子類之間,否則編譯報錯。

5、引用資料型別之間轉換的注意事項

  • 若強制的目標型別並不是該引用真正指向的資料型別時,則編譯通過,執行階段發生型別轉換異常。

  • 為了避免上述錯誤的發生,應該在強轉之前進行判斷,格式如下:

    if(引用變數 instanceof 資料型別)

    判斷引用變數指向的物件是否為後面的資料型別

package com;

public class Shape {

	//用private關鍵字私有化成員變數
	private int x;//宣告私有成員變數x描述橫座標
	private int y;//宣告私有成員變數y描述縱座標
	
	//自定義構造方法進行合理值判斷
	public Shape() {}
	public Shape(int x, int y) {
		
		setX(x);
		setY(y);
	}
	//提供get、set方法獲取私有成員變數
	public int getX() {
		
		return x;
	}
	
	public void setX(int x) {
		
		this.x = x;
	}
	
	public int getY() {
		
		return y;
	}
	
	public void setY(int y) {
		
		this.y = y;
	}
	
	//自定義成員方法列印所有特徵
	public void show() {
		
		System.out.println("橫座標是:" + getX() + ",縱座標是:" + getY());
	}
	
	//自定義靜態方法
	public static void test() {
		
		System.out.println("Shape類中的靜態方法");
	}
}



package com;

public class Rect extends Shape {

	//用private關鍵字私有化成員變數
	private int length;
	private int width;
	
	//自定義構造方法進行合理值判斷
	public Rect() {}
	public Rect(int x, int y, int length, int width) {
		
		super(x, y);
		setLength(length);
		setWidth(width);
	}
	//提供get、set方法進行合理值判斷
	public int getLength() {
		
		return length;
	}
	
	public void setLength(int length) {
		
		if(length > 0) {
			
			this.length = length;
		}else {
			
			System.out.println("長度有誤!");
		}
	}
	
	public int getWidth() {
		
		return width;
	}
	
	public void setWidth(int width) {
		
		if(width > 0) {
			
			this.width = width;
		}else {
			
			System.out.println("寬度有誤!");
		}
	}
	
	//自定義成員方法列印所有特徵
	@Override
	public void show() {
		
		super.show();
		System.out.println("長是:" + getLength() + "寬是:" + getWidth());
	}
	
	//自定義靜態方法
	//@Ovrride Error:歷史原因 靜態方法不是真正意義上的重寫
	public static void test() {
			
		System.out.println("Rect類中的靜態方法");
	}
}



package com;

public class Circle extends Shape{

	private int ir;
	
	public Circle() {}
	public Circle(int x, int y, int ir) {
		
		super(x, y);
		setIr(ir);
	}
	
	public int getIr() {
		
		return ir;
	}
	
	public void setIr(int ir) {
		
		if(ir > 0) {
			
			this.ir = ir;
		}else {
			
			System.out.println("半徑不合理!");
		}
	}
	
	public void show() {
		
		super.show();
		System.out.println("圓的半徑是:" + getIr());
	}
}



package com;

public class ShapeRectTest {
	
	public static void main(String[] args) {
		
		//宣告Shape型別的引用指向該型別的物件
		Shape s = new Shape(1, 2);
		//當Rect類中沒有重寫show方法時,下面呼叫shape類中的show方法
		//當Rect類中重寫show方法時,下面呼叫shape類中的show方法
		s.show();
		
		//宣告Rect型別的引用指向該型別的物件
		System.out.println("-----------------------");
		Rect r = new Rect(3, 4, 5, 6);
		//當Rect類中沒有重寫show方法時,下面呼叫shape類中的show方法
		//當Rect類中重寫show方法時,下面呼叫Rect類中的show方法
		r.show();
		
		//宣告Shape型別的引用指向Rect型別的物件
		System.out.println("-----------------------");
		//多型相當於從子類Rect型別到父類Shape型別的轉換,小到大的轉換,也就是自動型別轉換
		Shape sr = new Rect(7, 8, 9, 10);
		//當Rect類中沒有重寫show方法時,下面呼叫shape類中的show方法
		//當Rect類中重寫show方法時,下面程式碼在編譯階段呼叫shape類中show方法,在執行階段呼叫Rect類中的show方法
		sr.show();
		
		System.out.println("-----------------------");
		//測試Shape型別的引用能否直接呼叫父類和子類獨有的方法
		int ia = sr.getX();
		System.out.println("獲取到的橫座標是:" + ia);
		//int ib = sr.getLength(); error Shape類中找不到getLength方法,也就是還是在Shape類中查詢
		//呼叫靜態方法
		sr.test();
		Shape.test();
		r.test();
		
		System.out.println("-----------------------");
		//使用父類型別的引用呼叫子類獨有方法的方式
		int ib = ((Rect)sr).getLength();//強制型別轉換 把Shape型別的引用強制轉換為Rect型別
		System.out.println("獲取到的長度是:" + ib);
		//希望將Shape型別強制轉換為Circle型別,編譯沒有報錯
		//Circle c1 = (Circle)sr;//編譯ok,但執行階段發生ClassCastException型別轉換異常
		
		//在強制型別轉換之前應該使用instanceof進行型別的判斷
		//判斷sr指向的堆區記憶體的物件是否為Circle
		if(sr instanceof Circle) {
			
			System.out.println("可以放心的轉換");
			Circle c1 = (Circle)sr;
		}else {
			
			System.out.println("強轉有風險");
		}
	}
}

/*
輸出:
橫座標是:1,縱座標是:2
-----------------------
橫座標是:3,縱座標是:4
長是:5寬是:6
-----------------------
橫座標是:7,縱座標是:8
長是:9寬是:10
-----------------------
獲取到的橫座標是:7
Shape類中的靜態方法
Shape類中的靜態方法
Rect類中的靜態方法
-----------------------
獲取到的長度是:9
強轉有風險

*/

6、多型的實際意義

  • 多型的實際意義在於遮蔽不同子類的差異性實現通用的程式設計帶來的不同的效果。
package com;

public class ShapeTest {
 
	//自定義成員方法實現將引數指定矩形物件特徵打印出來的行為,也就是繪製圖形的行為
	/*public static void draw(Rect r) {
		
		r.show();
	}
	
	//自定義成員方法實現將指定圓形物件特徵打印出來的行為
	public static void draw(Circle c) {
		
		c.show();
	}*/
	
	//自定義成員方法實現既能列印矩形物件又能列印圓形物件的特徵,物件有引數傳入 子類 is a 父類
	//Shape s = new Rect(1, 2, 3, 4); 父類型別的引用指向子類型別的物件,形成了多型
	//Shape s = new Circle(5, 6, 7);   多型
	//多型的使用場合之一:通過引數傳遞形成了多型
	public static void draw(Shape s) {
		
		//編譯階段呼叫父類的版本,執行階段呼叫子類重寫以後的版本
		s.show();
	}
	
	public static void main(String[] args) {
		
		ShapeTest.draw(new Rect(1, 2, 3, 4));
		System.out.println("-------------------");
		ShapeTest.draw(new Circle(5, 6, 7));
	}
}
/*
輸出:
橫座標是:1,縱座標是:2
長是:3寬是:4
-------------------
橫座標是:5,縱座標是:6
圓的半徑是:7
*/

二、抽象

1、抽象方法的概念

  • 抽象方法主要指不能具體實現的方法並且使用abstract關鍵字修飾,也就是沒有方法體。

  • 具體格式如下:

    訪問許可權 abstract 返回值型別 方法名(形參列表);

    public absract void cry();

2、抽象類的概念

  • 抽象類主要指不能具體例項化的類並且使用abstract關鍵字修飾,也就是不能建立物件。(抽象類不能建立物件的意義:防止呼叫抽象方法)

3、抽象類和抽象方法的關係

  • 抽象類中可以有成員變數、構造方法、成員方法;
  • 抽象類中可以沒有抽象方法,也可以有抽象方法
  • 擁有抽象方法的類必須是抽象類,因此真正意義上的抽象類應該是具有抽象方法並且使用abstract關鍵字修飾的類。

4、抽象類的實際意義

  • 抽象類的實際意義不在於建立物件而在於被繼承。
  • 當一個類繼承抽象類後必須重寫抽象方法,否則該類也變成抽象類,也就是抽象類對子類具有強制性和規範性,也就是模板設計模式。
package com;

public abstract class AbstractTest {
	
	private int cnt;
	
	public AbstractTest() {}
	public AbstractTest(int cnt) {
		
		setCnt(cnt);
	}
	public int getCnt() {
		
		return cnt;
	}
	
	public void setCnt(int cnt) {
		
		this.cnt = cnt;
	}
	
	//自定義抽象方法
	public abstract void show();
	
	public static void main(String[] args) {
		
		//AbstractTest at = new AbstractTest();
		//System.out.println("at.cnt = " + at.getCnt());
	}
}




package com;

public class SubAbstractTest extends AbstractTest {

	@Override
	public void show() {
		// TODO Auto-generated method stub
		System.out.println("其實我是被迫重寫的,否則我也得程式設計抽象類!");
	}

	public static void main(String[] args) {
		
		SubAbstractTest sst = new SubAbstractTest();
		sst.show();
		
		System.out.println("-----------------------------------------");
		//宣告AbstractTest型別的引用指向子類的物件,形成多型
		//多型的使用場合之二:直接在方法體中使用抽象型別的引用指向子類型別的物件
		AbstractTest at = new SubAbstractTest();
		//編譯階段呼叫父類版本,執行階段呼叫子類版本
		at.show();
	}
	
}

5、開發經驗分享

  • 在以後的開發中推薦使用多型的格式,此時父類型別引用直接呼叫的所有方法一定是父類中擁有的方法,若以後更換子類時,只需要將new關鍵字後面的子類類修改而其他地方無需改變就可以立即生效,從而提高了程式碼的可維護性和可擴充套件性。
  • 該方式的缺點就是:父類引用不能直接呼叫子類獨有的方法,若呼叫則需要強制型別轉換。

6、案例:

銀行有 定期賬戶和活期賬戶。

繼承自 賬戶類。

賬戶類中:

public class Account{

​ private double money;

​ public double getLixi(){}

}

package com;

public abstract class Account {
	
	private double money;
	//private double lixi;
	
	//自定義構造方法
	public Account() {}
	public Account(double money) {
		
		setMoney(money);
	}
	
	//提供公有的get、set方法獲取私有成員變數
	public double getMoney() {
		
		return money;
	}
	
	public void setMoney(double money) {
		
		this.money = money;
	}
	
	//宣告抽象方法計算利息並返回
	public abstract double getLixi();
	
	//public abstract void setLixi(double lixi);
	
	//自定義成員方法列印所有特徵
	public void show() {
		
		System.out.println("賬戶餘額是:" + getMoney());
	}
}


package com;

public class DAccount extends Account{

	private double lixi;
	
	public DAccount() {}
	public DAccount(double money) {
		
		super(money);
	}
	@Override
	public double getLixi() {
		// TODO Auto-generated method stub
		lixi = getMoney() * 0.001;
		return lixi;
	}
	
	/*public void setLixi(double lixi) {
		
		lixi = getMoney() * 0.001;
		this.lixi = lixi;
	}*/

	public void show() {
		
		super.show();
		System.out.println("定期利息是:" + getLixi());
	}
}


package com;

public class HAccount extends Account {

	//private double lixi;
	
	public HAccount() {}
	public HAccount( double money) {
		
		super(money);
	}
	
	@Override
	public double getLixi() {
		// TODO Auto-generated method stub
		double lixi;
		lixi = getMoney() * 0.0001;
		return lixi;
	}

	/*public void setLixi(double lixi) {
		
		lixi = getMoney() * 0.0001;
		this.lixi = lixi;
	}*/
	
	public void show() {
		
		super.show();
		System.out.println("活期的利息是:" + getLixi());
	}
}


package com;

public class AccountTest {

	public static void main(String[] args) {
		//宣告ccount型別的引用指向子類型別的物件,形成多型
		Account a1 = new DAccount(50000);
		a1.show();
		
		System.out.println("---------------------");
		Account a2 = new HAccount(50000);
		a2.show();
	}
}

7、筆試考點

//private 和abstract關鍵字不能共同修飾一個方法
//private abstract double getLixi();

//final 和abstract關鍵字不能共同修飾一個方法
//public final abstract double getLixi();

//static 和abstract關鍵字不能共同修飾一個方法
//public static abstract double getLixi();

三、介面

1、介面的概念

  • 介面就是一種比抽象還抽象的類,體現在所有方法都是抽象方法

    package com;
    
    public interface InterfaceTest {
    
    	/*public static final*/ int CNT = 1;//接口裡面只能有常量
    	//private void show(){}
    	
    	/*public abstract*/ void show(); //裡面只能有抽象方法(新特性除外) 註釋中的關鍵字可以省略,但建議寫上
    }
    
    
  • 定義類的關鍵字是class,而定義介面的關鍵字是interface。

    如:金屬介面、貨幣介面、黃金類

package com;

public interface Metal {

	//自定義抽象方法描述發光的行為
	public abstract void shine();
}


package com;

public interface Money {

	//自定義抽象方法描述購物行為
	
	public abstract void buy();
}


package com;

//使用implements關鍵字表示實現的關係,支援多實現
public class Gold implements Metal, Money {

	@Override
	public void shine() {
		
		System.out.println("發出了金黃色的光芒!");
	}
	
	@Override
	public void buy() {
		
		System.out.println("買了好多好吃的。。。。");
	}
	
	public static void main(String[] args) {
		
		//宣告介面型別的引用指向實現類的物件
		Metal mt = new Gold();
		mt.shine();
		
		Money mn = new Gold();
		mn.buy();
	}
}

2、類與介面之間的關係

名稱			關鍵字                       關係
類與類之間	   使用extends表達繼承關係          支援單繼承
類與介面之間	   使用implements表達實現關係       支援多實現
介面與介面之間	   使用extends表達繼承關係          支援多繼承
package com;

public interface Runner {

	//自定義抽象方法描述奔跑行為
	public abstract void run();
}


package com;

public interface Hunter extends Runner{
	
	//自定義抽象方法描述捕獵的行為
	public abstract void hunt();
}


package com;

public class Man implements Hunter {

	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println("追逐一隻兔子。。。");
	}

	@Override
	public void hunt() {
		// TODO Auto-generated method stub
		System.out.println("用弓箭射殺兔子。。。。");
	}

	public static void main(String[] args) {
		
		//宣告介面型別的引用指向實現類的物件,形成多型
		Runner runner = new Man();
		runner.run();
		
		Hunter hunter = new Man();
		hunter.hunt();
	}
	
}

3、抽象類和介面的主要區別(筆試題

  • 定義抽象類的關鍵字是abstract class,而定義介面的關鍵字是interface。
  • 繼承抽象類的關鍵字是extends,而實現介面的關鍵字是implements。
  • 繼承抽象類支援單繼承,而實現介面支援多實現。
  • 抽象類中可以有構造方法,而介面中不可以有構造方法。
  • 抽象類中可以有成員變數,而介面中只可以有常量。
  • 抽象類中可以有成員方法,而介面中只可以有抽象方法。
  • 抽象類中增加方法時子類可以不用重寫,而介面中增加方法時,實現類需要重寫(Java8以前的版本)
  • 從Java8開始增加新特性,介面中允許出現非抽象方法和靜態方法,但非抽象方法需要使用default關鍵字修飾。
  • 從Java9開始增加新特性,介面中允許出現私有方法。
package com;

public interface Hunter extends Runner{
	
	//自定義抽象方法描述捕獵的行為
	public abstract void hunt();
	
	//增加一個抽象方法
	//public abstract void show();
	
	//增加一個show方法
	private void show() {
		
		System.out.println("這裡僅僅是介面中的預設功能,實現類可以自由選擇是否重寫!");
	}
	//增加一個非抽象方法
	public default void show1() {
	 	//System.out.println("儘可能避免重複程式碼的使用!");
	 	 show();
		 System.out.println("這裡僅僅是介面中的預設功能,實現類可以自由選擇是否重寫!");
	}
	
	public default void show2() {
	 	//System.out.println("這裡僅僅是介面中的預設功能,實現類可以自由選擇是否重寫!");
	 	show();
		System.out.println("這裡僅僅是介面中的預設功能,實現類可以自由選擇是否重寫!");
}
	
	//增加靜態方法 隸屬於類層級,也就是介面層級
	public static void test() {
		
		System.out.println("這裡是靜態方法,可以直接通過介面名.的方式呼叫,省略物件的建立");
	}
}

文章內容輸出來源:拉勾教育Java高薪訓練營