Java多態性抽象類與接口細致詳解

1、多態性

多態性是面向對象的最後一個特征,它本身主要分為兩個方面:

​ 方法的多態性:重載與覆寫

​ 1 重載:同一個方法名稱,根據參數類型以及個數完成不同功能;

​ 2 覆寫:通一個方法,根據操作的子類不同,所完成的功能也不同。

​ 對象的多態性:父子類對象的轉換。

​ 1 向上轉型:子類對象變為父類對象,父類 父類對象 = 子類 子類對象 自動;

​ 2 向上轉型:父類對象變為子類對象,格式:子類 子類對象 = (子類)父類實例,強制;

class A{
	public void print(){
		System.out.println("A輸出");
	}
}
class B extends A{
	public void print(){
		System.out.println("B輸出");
	}
}
public class TestDemo1{
	public static void main(String args[]){
		B b = new B();
		//B輸出
		b.print();
	}
}

這種操作主要觀察兩點:

1 看實例化的是哪一類(new);

2 看new的這個類之中是否被覆寫瞭父類調用的方法。

1.1 向上轉型

public class TestDemo1{
	public static void main(String args[]){
		A a = new B();  //向上轉型
        //B輸出
		a.print();
	}
}

1.2 向下轉型

public class TestDemo1{
	public static void main(String args[]){
		A a = new B();  //向上轉型
        B b = (B) a;    //向下轉型
		b.print();
	}
}
public class TestDemo1{
	public static void main(String args[]){
		A a = new A();  //沒有轉型
        B b = (B) a;     //向下轉型
		b.print();
	}
}
/*
Exception in thread "main" java.lang.ClassCastException: A cannot be cast to B
        at DuoTaiXing.main(TestDemo1.java:14)
*/

以上的程序在編譯的時候沒有發生任何錯誤信息,但是在執行的時候出現瞭“ClassCastException”錯誤提示,表示的是類轉換異常,即:兩個沒有關系的類互相發生瞭對象的強制轉型。

轉型的因素:

​ 1 在實際工作之中,對象的向上轉型為主要使用,向上轉型之後,多有方法以父類的方法為主,但是具體的實現,還是要看子類是否覆寫瞭此方法;。

​ 2 向下轉型,因為在進行向下轉型操作之前,一定要首先發生向上轉型,以建立兩個對象之間的聯系,如果沒有這種聯系,是不可能發生向下轉型的,一旦發生瞭運行中就會出現“ClassCastException”當需要調用子類自己特殊定義方法的時候,菜需要向下轉型。

​ 3 不轉型,在一些資源較少的時候,如:移動開發。

class A{
	public void print(){
		System.out.println("A輸出");
	}
}
class B extends A{
	public void print(){
		System.out.println("B輸出");
	}
	public void funB(){
		System.out.println("funB");
	}
}
public class TestDemo1{
	public static void main(String args[]){
		A a = new B();    //向上轉型
		System.out.println(a instanceof A);
		System.out.println(a instanceof B);
		if (a instanceof B){
			B b = (B) a;
			b.funB();
		}
	}
}
/*
true
true
funB
*/

為瞭日後的操作方便,在編寫代碼的時候,盡量不要去執行向下的轉型操作,子類盡量不要去擴充新的方法名稱(父類沒有的方法名稱),依據父類定義的操作完善方法。

例題:利用對象向上轉型完成

class A{
	public void print(){
		System.out.println("A輸出");
	}
}
class B extends A{
	public void print(){
		System.out.println("B輸出");
	}
}
class C extends A{
	public void print(){
		System.out.println("C輸出");
	}
}
public class TestDemo2{
	public static void main(String args[]){
		fun(new B());
		fun(new C());
	}
	public static void fun(A a){
		a.print();
	}
}

這樣以來就得到瞭類型的統一,就算有再多的子類出現,方法或者是類也不需要進行修改瞭,但是在這塊必須強調的是:子類操作的過程之中,盡量向父類靠攏。

以後所有的代碼之中,都會存在對象的轉型問題,並且向上轉型居多。

在日後的所有開發之中,像之前程序那樣,一個類去繼承另外一個已經實現好的類的情況,是不可能出現的。即:一個類不能去繼承一個已經實現好的類,隻能繼承抽象類或實現接口。

2、抽象類

在以後你進行的項目開發中,絕對不要出現一個類繼承一個已經實現好的類。

對象多態性的核心本質在於方法的覆寫,這樣的操作有些不合要求,所以要對子類的方法進行強制的要求就必須采用我們的抽象類進行實現。

2.1 抽象類的基本概念

普通類就是一個完善的功能類,可以直接產生對象並且可以使用,裡面的方法都是帶有方法體的,而抽象類之中最大的特點是包含瞭抽象方法,而抽象方法是隻聲明而未實現(沒有方法體)的方法,而抽象方法定義的時候要使用abstract關鍵字完成,而抽象方法一定要在抽象類之中,抽象類要使用是用abstract聲明。

abstract class A{
	private String msg = "www.baidu.com";//屬性
	public void print(){	//普通方法
		System.out.println(msg);
	}
	//{}為方法體,所有的抽象方法上是不包含有方法體的
	public abstract void fun() ; //抽象方法
}

public class ChouXiang{
	public static void main(String args[]){
	/*
	ChouXiang.java:12: 錯誤: A是抽象的; 無法實例化
                A a =new A();
                     ^
		1 個錯誤

	*/
		A a =new A();
	}
}

抽象類比普通類多瞭抽象方法而已,沒有上面特殊的。

抽象方法為什麼不能實例化對象?

抽象類中包含抽象方法,而抽象方法與普通方法最大的區別就是沒有方法體,不知道具體的實現,而如果產生瞭實例化就意味著以可以調用類中的所有操作。

抽象類的使用原則:

  • 所有抽象類必須要含有子類
  • 抽象類的子類必須覆寫抽象中的全部抽象方法——方法覆寫一定要考慮到權限問題,抽象方法可以使用任意權限,要求權限盡量public
  • 抽象對象可以通過對象多態性,利用子類為其實現實例化
abstract class A{
	private String msg = "www.baidu.com";//屬性
	public void print(){	//普通方法
		System.out.println(msg);
	}
	//{}為方法體,所有的抽象方法上是不包含有方法體的
	public abstract void fun() ; //抽象方法
}
//一個子類隻能夠利用extends來繼續繼承抽象類,所以依然存在單繼承局限 
class B extends A{//定義抽象類的子類
	public void fun(){
		System.out.println("Hello");
	}
}
public class ChouXiang{
	public static void main(String args[]){
		A a =new B();//向上轉型
		a.print();
		a.fun();
	}
}

通過以上的一個程序,現在就可以清楚的發現,與之前類不一樣的是,抽象類定義出瞭子類必須要覆寫的方法,而之前的類子類可以有選擇性的來決定是否覆寫。而且可以發現,抽象類實際上就比普通類多瞭一些抽象方法而已,其他的定義和普通類完全一樣。如果把普通類比喻成一盤炒熟的菜,那麼抽象類就是一盤半成品。

關於抽象類的若幹中疑問?

1、抽象類能否使用final定義?

不能,因為final定義的類不能有子類,而抽象類必須有子類。

2、抽象類之中能否包含構造方法?

可以,因為抽象類之中除瞭包含抽象方法之外,還包含瞭普通方法和屬性,而屬性一定要在構造方法執行完畢之後才可以進行初始化操作;

3、抽象類之中能否不包含抽象方法?

可以,抽象類之中可以沒有抽象方法,但是反過來來講,如果有抽象,則一定是抽象類。即使抽象類之中沒有抽象方法也不能被直接實例化。

4、抽象類能否使用static聲明?

如過定義的是外部抽象類,則不能夠使用static聲明,可是如果定義的是內部類,那麼這個內部的抽象類使用瞭static聲明之後,就表示一個外部的抽象類。

abstract class A{
	private String str = "Hello,China";
	static abstract class B{
		public abstract void print();
	}	
}
class C extends A.B{
	public void print(){
		System.out.println("你好,中國");
	}
}
public class ChouXiang{
	public static void main(String args[]){
		A a =new B();//向上轉型
		a.print();
	}
}

結論:如果構造方法沒有執行,類中對象中屬性一定都是其對應數據類型的默認值。

抽象類的最大特點在於強制規定瞭子類的實現結構。

3、接口

抽象類和普通類最大的特點就是約定瞭子類的實現要求:但是抽象類有一個缺點——單繼承局限,如果要想要求以及避免單繼承局限,就需要使用接口。在以後的開發中,接口優先,在一個操作中既可以使用抽象類又可以使用我們的接口,優先考慮接口。

3.1 接口的基本概念

接口就是一個抽象方法和全局常量的集合,屬於一種特殊的類,如果一個類定義的時候全部由抽象方法和全局常量所組成的話,那麼這種類就稱為接口,但是接口是使用interface關鍵字定義的。

interface A{//定義接口
	public static final String INFO="Hello,World";
	public abstract void print();
}
interface B{
	public abstract void get();
}

那麼在接口之中,也同樣存在瞭抽象方法,很明顯,接口對象無法進行對象的實例化操作,那麼接口的使用原則如下:

1、每一個接口必須定義子類,子類使用implement關鍵字實現接口;

2、接口的子類(如果不是抽象類)則必須覆寫接口之中所定義的全部抽象方法;

3、利用接口的子類,采用對象的向上轉型方式,進行接口對象的實例化操作。

image-20210805114718379

在Java之中每一個抽象類都可以實現多個接口,但是反過來講,一個接口卻不能繼承抽象類,可是Java之中,一個接口卻可以同時繼承多個接口,以實現接口的多繼承操作。

//因為接口和類的定義命名要求相同,所以為瞭區分接口和類
//建議在所以的接口前面追加一個字母I
interface IMessage{
	public static final String MSG = "www.baidu.com";
	public abstract void print();//抽象方法
}
interface INews{
	public abstract String get();
}
class MessageImpl implements IMessage,INews{
	public void print(){
		System.out.println("IMessage中print方法:" +IMessage.MSG);
	}
	public String get(){
		return "INews中get方法:" + IMessage.MSG;
	}
}
class NewsImpl implements INews{
	public String get(){
		return null;
	}
}
public class InFa{
	public static void main(String args[]){
		IMessage ms = new MessageImpl();
		//InFa
		ms.print();
		INews m = new MessageImpl();
		//INews中get方法:www.baidu.com
		System.out.println(m.get());
		
		/*
Exception in thread "main" java.lang.ClassCastException: NewsImpl cannot be cast to IMessage
at InFa.main(InFa.java:33)
轉換異常
		*/
		INews mn = new NewsImpl();
		IMessage m1 = (IMessage) mn;
		System.out.println(mn.get());
	}
}

但是需要說明的是:接口之中的全部組成就是抽象方法和全局常量,那麼在開發之中一下的兩種定義接口的最終效果是完全一樣的:

3.2 接口的使用限制

完整定義:

interface A{ 
	public static final String INFO="接口A";
	public abstract void print();
}

簡化定義:

interface A{//定義接口
	 public String INFO="接口A";
	 public void print();
}

接口之中所有訪問權限隻有一種:public,即:定義接口方法的時候就算沒寫上public,最終也是public.

  1. 在以後的編寫接口的時候,大部分的接口裡面隻會提供抽象方法,很少在接口裡面看見許多的全局常量。很多時候防止避免開發者出現混亂,所以接口的方法都會加上public。
  2. 當一個子類需要實現接口又需要繼承抽象類的時候,請先使用extends繼承一個抽象類,再使用implements實現多個接口。
//因為接口和類的定義命名要求相同,所以為瞭區分接口和類
//建議在所以的接口前面追加一個字母I
interface INews{
	public String get();//抽象方法
}
//可以再類上進行明確描述,在以後的開發之中也經常出現以下的命名習慣
abstract class AbstractMessage{
//隻有接口中的abstract中才可以省略,抽象類中的不能省略
	public abstract void print();
}
class NewsImpl extends AbstractMessage implements INews{
	public String get(){
		return "www.baidu.com";
	}
	public void print(){}	//有方法體就叫覆寫
}
public class InFa1{
	public static void main(String args[]){
		INews news = new NewsImpl();
		System.out.println(news.get());
		//NewsImpl是抽象類和接口的共同子類
		AbstractMessage am = (AbstractMessage) news;
		am.print();
	}
}

3.一個抽象類可以使用implements實現多個接口,但是接口不能夠去繼承抽象類;

//因為接口和類的定義命名要求相同,所以為瞭區分接口和類
//建議在所以的接口前面追加一個字母I
interface INews{
	public String get();//抽象方法
}
//可以再類上進行明確描述,在以後的開發之中也經常出現以下的命名習慣
abstract class AbstractMessage implements INews{
//隻有接口中的abstract中才可以省略,抽象類中的不能省略
	public abstract void print();
}
class NewsImpl extends AbstractMessage{
	public String get(){
		return "www.baidu.com";
	}
	public void print(){}	//有方法體就叫覆寫
}
/*
該類的調用相當於孫子實例化爺爺對象 將 爺爺對象轉換為兒子對象
*/
public class InFa1{
	public static void main(String args[]){
		INews news = new NewsImpl();
		System.out.println(news.get());
		//NewsImpl是抽象類和接口的共同子類
		AbstractMessage am = (AbstractMessage) news;
		am.print();
	}
}

實際上此時關系屬於三層繼承。

接口與抽象類

//因為接口和類的定義命名要求相同,所以為瞭區分接口和類
//建議在所以的接口前面追加一個字母I
interface INews{
	public String get();//抽象方法
	public void pirnt();  
}
//假設一個接口可能有無數個子類,但是對於一個方法的實現是一樣的
abstract class AbstractMessage implements INews{

	public  void print(){
		System.out.println("www.baidu.com");
	}
}
//生怕別人不知道NewsImpl是INews接口的子類,做一個重復標記而已
class NewsImpl extends AbstractMessage implements INews{
	public String get(){
		return "www.baidu.com";
	}
	public void print(){}	//有方法體就叫覆寫
}
/*
該類的調用相當於孫子實例化爺爺對象 將 爺爺對象轉換為兒子對象
*/
public class InFa1{
	public static void main(String args[]){
		INews news = new NewsImpl();
		System.out.println(news.get());
		//NewsImpl是抽象類和接口的共同子類
		AbstractMessage am = (AbstractMessage) news;
		am.print();
	}
}

image-20210805172013189

4.一個接口可以使用extends來繼承多個父接口。

interface A{
	public void pirntA();  
}
interface B{
	public void pirntB();  
}
interface C extends A,B{
	public void pirntC();
}
class Impl implements C{
	public void pirntA();  
	public void pirntB();  
	public void pirntC();  
}

public class InFa1{
	public static void main(String args[]){
	}
}

5.接口可以定義一系列的內部接口,包括:內部普通類、內部抽象類、內部接口,其中使用static定義的內部接口就相當於是一個外部接口

而在開發之中,內部類是永遠不會受到概念限制的,在一個類中可以定義內部類,在一個抽象類之中也可以抽象內部類,在一個接口裡面也可以定義內部抽象類或內部接口.但是從實際的開發來講用戶去定義內部抽象類或內部接口的時候是比較少見的(android開發中見過),而且在定義內部接口的時候如果使用瞭static,表示一個外部接口。

interface A{
	public void printA();
static interface B{  //外部接口
		public void printB();
	}
}
class X implements A.B{
	public void printA(){
		System.out.println("A");
	}
	public void printB(){
		System.out.println("B");
	}
}
public class Inter{
	public static void main(String args[]){
		A.B ab = new X();
		ab.printB();
	}
}

3.3 使用接口定義標準

以上對於接口的概念並不是很難理解,但是需要強調的是,在實際開發之中,接口有三大主要功能:

  • 制定操作標準;
  • 表示一種能力;
  • 將服務器端的遠程方法試圖暴露給客戶端。

image-20210805175619590

定義USB接口:

interface USB{
	public void install();	//進行安裝
	public void work();	//進行工作
}

定義USB的子類:

class Computer{
	public void plugin(USB usb){
		usb.install();
		usb.work();
	}
}
class Flash implements USB{
	public void install(){
		System.out.println("安裝U盤驅動");
	}
	public void work(){
		System.out.println("U盤進行工作");
	}
}
class Printer implements USB{
	public void install(){
		System.out.println("安裝打印機驅動");
	}
	public void work(){
		System.out.println("打印機進行工作");
	}
}

源代碼:

interface USB{
	public void install();	//進行安裝
	public void work();	//進行工作
}
class Computer{
	public void plugin(USB usb){
		usb.install();
		usb.work();
	}
}
class Flash implements USB{
	public void install(){
		System.out.println("安裝U盤驅動");
	}
	public void work(){
		System.out.println("U盤進行工作");
	}
}
class Printer implements USB{
	public void install(){
		System.out.println("安裝打印機驅動");
	}
	public void work(){
		System.out.println("打印機進行工作");
	}
}
public class InFa3{
	public static void main(String args[]){
	/*
安裝U盤驅動
U盤進行工作
安裝打印機驅動
打印機進行工作
	*/
		Computer cm = new Computer();
		cm.plugin(new Flash());
		cm.plugin(new Printer());
	}
}

發現使用接口和對象多態性結合,對於參數的統一更加明確。而且可以發現接口時再類之上的設計。

image-20210805181329318

3.4 抽象類與接口的區別

區別 抽象類 接口
定義關鍵字 使用abstract class進行聲明 使用interface進行聲明
組成 全局常量、全局變量、構造方法、抽象方法、普通方法 全局變量、抽象方法
權限 可以使用任何權限 隻能使用public權限
關系 一個抽象類可以實現多個接口 接口不能繼承抽象類,卻可以繼承多個接口
使用 子類使用extends 子類使用implements
設計模式 模板設計模式 工廠模式、代理模式
局限 一個子類隻能繼承一個抽象方法 一個子類可以實現多個接口

通過上面的分析可以得出結論:在開發之中,抽象類和接口實際上都是可以使用的,並且使用哪一個都沒有明確的限制,可是抽象類有一個最大的缺點—一個子類隻能夠繼承一個抽象類,存在單繼承的局限,所以當遇到抽象類和接口都可以實現的情況下,優先考慮接口,避免單繼承局限。

除瞭單繼承的局限之外,實際上使用抽象類和接口都是類似的,但是在實際的開發中,抽象類的設計比接口的復雜。

image-20210806112850364

到此這篇關於Java多態性抽象類與接口細致詳解的文章就介紹到這瞭,更多相關Java多態性抽象類與接口內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: