C#和Java關於類、抽象類、接口使用方式基本類似,只是對應關鍵字使用不一樣罷了,本節呢,咱們只是對照C#和Java中關於這三個概念在具體使用時,看看有哪些不同的地方。html
C#和Java在定義類方式上是一致的,這點沒有什麼太多要講解的,咱們直接進入到類繼承上,在Java中實現繼承經過extends關鍵字,而在C#中則是以冒號(:)來繼承,很是優雅而簡潔,Java以下:java
class Animal{} class Tiger extends Animal{}
在C#中以下:編程
class Animal { } class Tiger : Animal { }
既然講解到了繼承,必然也就涉及到方法重寫了,不管Java仍是C#對於重寫的概念一致:方法重寫意味着在子類中定義一個方法,該子類已經在父類中定義,具備相同的方法簽名 - 相同的名稱,參數和返回類型。Java中對於重寫以下:electron
class Animal { void Run() { System.out.println("動物可能會跑"); } } class Tiger extends Animal { void Run() { System.out.println(this.getClass().getSimpleName() + "會跑"); } }
Tiger tiger = new Tiger();
tiger.Run();
在C#中咱們也能夠如上代碼進行,可是會有警告,以下:ide
class Animal { public void Run() { Console.WriteLine("動物可能會跑"); } } class Tiger : Animal { public void Run() { Console.WriteLine($"{GetType().Name}會跑"); } }
咱們經過如上寫了以後,咱們會發現編譯器提示以下警告,爲什麼呢?,不難想象,由於咱們子類繼承了父類,這就至關於父類的方法就在子類中同樣(咱們這裏說的是至關於,由於若是在一個類中要是有兩個如出一轍的方法也就是方法名稱、簽名都同樣,確定就報錯了,要區分開重載和重寫的概念),因此會進行警告提示。學習
實際上在C#中的重寫是以下這樣的,父類方法若根據業務來看後續存在被重寫的可能則經過virtual關鍵字修飾,在子類中重寫父類中的方法時經過override關鍵字修飾this
class Animal { public virtual void Run() { Console.WriteLine("動物可能會跑"); } } class Tiger : Animal { public override void Run() { Console.WriteLine($"{GetType().Name}會跑"); } }
C#和Java中對於抽象類使用基本無差別,都是可定義抽象方法和非抽象方法,而抽象方法只能在抽象類和接口中,有的人就說了,爲什麼不能在類中定義,這就涉及到學習方法了,由於都是面向對象的語言,因此咱們就要以人的思惟方式去思考和舉例(不要每學一門語言就感受是全新的概念,不少都是相通的),由於類實例化後就是一個具體的對象,既然是具體的對象,那麼在對象中的變量和方法必須是徹底實現了的,這麼講想必咱們就恍然大悟、豁然開朗了。抽象方法在接口中的定義惟一一點的小區別則是在idea編譯器中會提示abstract徹底不必,由於接口就是抽象的類型,而在vs編譯中不會進行提示。那麼在Java中接口的定義是什麼呢?接口是一種抽象類型,包含方法和常量變量的集合, 它是Java中的核心概念之一,用於實現抽象,多態和多重繼承。接下來咱們定義一個電子產品接口,以下:idea
interface Electronic { //常量 String LED = "LED"; //抽象方法 int getElectricityUse(); //靜態方法 static boolean isEnergyEfficient(String electronicType) { if (electronicType.equals(LED)) { return true; } return false; } //默認方法 default void printDescription() { System.out.println("Electronic Description"); } }
接下來咱們來經過具體的電子產品來實現上述接口,經過implements關鍵字來實現接口。spa
class Computer implements Electronic { public int getElectricityUse() { return 1000; } }
接下來咱們進行以下調用:翻譯
System.out.println(Computer.LED); Computer computer = new Computer(); System.out.println(computer.getElectricityUse());
那麼問題來了,定義一個接口時,在接口中咱們能夠定義哪些內容呢?常量變量、抽象方法、靜態方法、默認方法。對於靜態方法和默認方法是在Java8中才出現的新特性,常量變量必須是以public、static、final修飾,這點好理解,咱們來經過類訪問新特性出現的靜態方法,結果以下訪問不到,這是啥狀況?
Java8新特性對於接口添加了靜態方法,既然是靜態方法咱們經過實現接口,經過類訪問靜態方法竟然訪問不到,那接口中的靜態方法還有存在的意義?是否是腦子有坑呢?是否是有點開始懷疑人生了呢?莫慌,咱們要學會分析問題:由於類能夠實現多個接口,若一個類實現了多個接口,並且多個接口中定義了相同的靜態方法, 此時類都將繼承多個接口中相同的靜態方法,此時會出現編譯器不知道要調用哪一個接口中的靜態方法的問題。因此纔出現了咱們實現了接口卻沒法訪問接口中的靜態方法,這是Java8中對於接口中定義靜態方法的限制即:接口中的靜態方法不能由實現它的類所繼承,只能經過其定義的接口訪問靜態方法。以下:
System.out.println(Electronic.isEnergyEfficient("LED"));
那麼問題又來了,要是咱們以下在實現接口中的類中也定義接口中的靜態方法,會不會出現重寫的狀況呢(爲了觀察是否重寫,將判斷條件取非)?
class Computer implements Electronic { public int getElectricityUse() { return 1000; } static boolean isEnergyEfficient(String electronicType) { if (!electronicType.equals(LED)) { return true; } return false; } }
System.out.println(Computer.isEnergyEfficient("LED"));
System.out.println(Electronic.isEnergyEfficient("LED"));
由上咱們知道其接口的實現類具備相同名稱的靜態方法,而且都不會重寫。那麼在接口中定義靜態方法的意義是什麼呢?在我看來:靜態方法本屬於類級別,在java8中將靜態方法擴展到接口,至關於咱們能夠將接口做爲類來使用。那麼問題又來了,在java8新特性中出現了靜態方法,也出現了默認方法,那麼靜態方法和默認方法有何區別呢?咱們在其接口實現類中添加和接口中默認方法同名的方法,以下:
class Computer implements Electronic { public int getElectricityUse() { return 1000; } static boolean isEnergyEfficient(String electronicType) { if (!electronicType.equals(LED)) { return true; } return false; } public void printDescription() { System.out.println("Computer Electronic"); } }
接下來咱們經過類實例和匿名類以下來調用上述方法:
//實例類 Electronic computer = new Computer(); computer.printDescription(); //匿名類 Electronic e = new Electronic() { @Override public int getElectricityUse() { return 50; } @Override public void printDescription() { System.out.println("Anonymous Electronic Description"); } }; e.printDescription();
咱們看到類實例和匿名類均可重寫默認方法,由上咱們可得出結論:接口靜態方法只能由定義的接口所調用,而接口默認方法可由接口實現類實例或接口匿名類所重寫,進一步闡述了在java8中接口被擴展到當作類使用。而接口默認方法的做用則是提供經常使用功能,一來擴展接口,二來不用破壞接口實現類。接口靜態方法的做用則是提供公共幫助方法而無需額外再建立類。在C#接口咱們能夠定義屬性,可是在Java中則不行,同時在C# 8.0上針對接口咱們也能夠定義默認方法,同時對於默認方法沒有任何限制,也就是說咱們既能夠定義普通方法,也能夠定義靜態方法,這點是Java不可比擬的,好比以下:
public interface Electronic { string Color { get; set; } bool isEnergyEfficient(string electronicType) { return true; } }
abstract class和interface在Java語言中都是用來進行抽象類(本文中的抽象類並不是從abstract class翻譯而來,它表示的是一個抽象體,而abstract class爲Java語言中用於定義抽象類的一種方法,請讀者注意區分)定義的,那麼什麼是抽象類,使用抽象類能爲咱們帶來什麼好處呢?《轉載:https://www.ibm.com/developerworks/cn/java/l-javainterface-abstract/index.html》
在面向對象的概念中,咱們知道全部的對象都是經過類來描繪的,可是反過來卻不是這樣。並非全部的類都是用來描繪對象的,若是一個類中沒有包含足夠的信息來描繪一個具體的對象,這樣的類就是抽象類。抽象類每每用來表徵咱們在對問題領域進行分析、設計中得出的抽象概念,是對一系列看上去不一樣,可是本質上相同的具體概念的抽象。好比:若是咱們進行一個圖形編輯軟件的開發,就會發現問題領域存在着圓、三角形這樣一些具體概念,它們是不一樣的,可是它們又都屬於形狀這樣一個概念,形狀這個概念在問題領域是不存在的,它就是一個抽象概念。正是由於抽象的概念在問題領域沒有對應的具體概念,因此用以表徵抽象概念的抽象類是不可以實例化的。
在面向對象領域,抽象類主要用來進行類型隱藏。咱們能夠構造出一個固定的一組行爲的抽象描述,可是這組行爲卻可以有任意個可能的具體實現方式。這個抽象描述就是抽象類,而這一組任意個可能的具體實現則表現爲全部可能的派生類。模塊能夠操做一個抽象體。因爲模塊依賴於一個固定的抽象體,所以它能夠是不容許修改的;同時,經過從這個抽象體派生,也可擴展此模塊的行爲功能。熟悉OCP的讀者必定知道,爲了可以實現面向對象設計的一個最核心的原則OCP( Open-Closed Principle),抽象類是其中的關鍵所在。
在語法層面,Java語言對於abstract class和interface給出了不一樣的定義方式,下面以定義一個名爲Demo的抽象類爲例來講明這種不一樣。
使用abstract class的方式定義Demo抽象類的方式以下:
使用interface的方式定義Demo抽象類的方式以下:
在abstract class方式中,Demo能夠有本身的數據成員,也能夠有非abstarct的成員方法,而在interface方式的實現中,Demo只可以有靜態的不能被修改的數據成員(也就是必須是static final的,不過在interface中通常不定義數據成員),全部的成員方法都是abstract的。從某種意義上說,interface是一種特殊形式的abstract class。
從編程的角度來看,abstract class和interface均可以用來實現"design by contract"的思想。可是在具體的使用上面仍是有一些區別的。首先,abstract class在Java語言中表示的是一種繼承關係,一個類只能使用一次繼承關係。可是,一個類卻能夠實現多個interface。也許,這是Java語言的設計者在考慮Java對於多重繼承的支持方面的一種折中考慮吧。其次,在abstract class的定義中,咱們能夠賦予方法的默認行爲。可是在interface的定義中,方法卻不能擁有默認行爲,爲了繞過這個限制,必須使用委託,可是這會 增長一些複雜性,有時會形成很大的麻煩。
在抽象類中不能定義默認行爲還存在另外一個比較嚴重的問題,那就是可能會形成維護上的麻煩。由於若是後來想修改類的界面(通常經過abstract class或者interface來表示)以適應新的狀況(好比,添加新的方法或者給已用的方法中添加新的參數)時,就會很是的麻煩,可能要花費不少的時間(對於派生類不少的狀況,尤其如此)。可是若是界面是經過abstract class來實現的,那麼可能就只須要修改定義在abstract class中的默認行爲就能夠了。一樣,若是不能在抽象類中定義默認行爲,就會致使一樣的方法實現出如今該抽象類的每個派生類中,違反了"one rule,one place"原則,形成代碼重複,一樣不利於之後的維護。所以,在abstract class和interface間進行選擇時要很是的當心。
上面主要從語法定義和編程的角度論述了abstract class和interface的區別,這些層面的區別是比較低層次的、非本質的。本小節將從另外一個層面:abstract class和interface所反映出的設計理念,來分析一下兩者的區別。做者認爲,從這個層面進行分析才能理解兩者概念的本質所在。
前面已經提到過,abstarct class在Java語言中體現了一種繼承關係,要想使得繼承關係合理,父類和派生類之間必須存在"is a"關係,即父類和派生類在概念本質上應該是相同的(參考文獻〔3〕中有關於"is a"關係的大篇幅深刻的論述,有興趣的讀者能夠參考)。對於interface 來講則否則,並不要求interface的實現者和interface定義在概念本質上是一致的,僅僅是實現了interface定義的契約而已。爲了使論述便於理解,下面將經過一個簡單的實例進行說明。
考慮這樣一個例子,假設在咱們的問題領域中有一個關於Door的抽象概念,該Door具備執行兩個動做open和close,此時咱們能夠經過abstract class或者interface來定義一個表示該抽象概念的類型,定義方式分別以下所示:
使用abstract class方式定義Door:
使用interface方式定義Door:
其餘具體的Door類型能夠extends使用abstract class方式定義的Door或者implements使用interface方式定義的Door。看起來好像使用abstract class和interface沒有大的區別。
若是如今要求Door還要具備報警的功能。咱們該如何設計針對該例子的類結構呢(在本例中,主要是爲了展現abstract class和interface反映在設計理念上的區別,其餘方面無關的問題都作了簡化或者忽略)?下面將羅列出可能的解決方案,並從設計理念層面對這些不一樣的方案進行分析。
解決方案一:
簡單的在Door的定義中增長一個alarm方法,以下:
那麼具備報警功能的AlarmDoor的定義方式以下:
這種方法違反了面向對象設計中的一個核心原則ISP(Interface Segregation Priciple),在Door的定義中把Door概念自己固有的行爲方法和另一個概念"報警器"的行爲方法混在了一塊兒。這樣引發的一個問題是那些僅僅依賴於Door這個概念的模塊會由於"報警器"這個概念的改變(好比:修改alarm方法的參數)而改變,反之依然。
解決方案二:
既然open、close和alarm屬於兩個不一樣的概念,根據ISP原則應該把它們分別定義在表明這兩個概念的抽象類中。定義方式有:這兩個概念都使用abstract class方式定義;兩個概念都使用interface方式定義;一個概念使用abstract class方式定義,另外一個概念使用interface方式定義。
顯然,因爲Java語言不支持多重繼承,因此兩個概念都使用abstract class方式定義是不可行的。後面兩種方式都是可行的,可是對於它們的選擇卻反映出對於問題領域中的概念本質的理解、對於設計意圖的反映是否正確、合理。咱們一一來分析、說明。若是兩個概念都使用interface方式來定義,那麼就反映出兩個問題:一、咱們可能沒有理解清楚問題領域,AlarmDoor在概念本質上究竟是Door仍是報警器?二、若是咱們對於問題領域的理解沒有問題,好比:咱們經過對於問題領域的分析發現AlarmDoor在概念本質上和Door是一致的,那麼咱們在實現時就沒有可以正確的揭示咱們的設計意圖,由於在這兩個概念的定義上(均使用interface方式定義)反映不出上述含義。
若是咱們對於問題領域的理解是:AlarmDoor在概念本質上是Door,同時它有具備報警的功能。咱們該如何來設計、實現來明確的反映出咱們的意思呢?前面已經說過,abstract class在Java語言中表示一種繼承關係,而繼承關係在本質上是"is a"關係。因此對於Door這個概念,咱們應該使用abstarct class方式來定義。另外,AlarmDoor又具備報警功能,說明它又可以完成報警概念中定義的行爲,因此報警概念能夠經過interface方式定義。以下所示:
這種實現方式基本上可以明確的反映出咱們對於問題領域的理解,正確的揭示咱們的設計意圖。其實abstract class表示的是"is a"關係,interface表示的是"like a"關係,你們在選擇時能夠做爲一個依據,固然這是創建在對問題領域的理解上的,好比:若是咱們認爲AlarmDoor在概念本質上是報警器,同時又具備Door的功能,那麼上述的定義方式就要反過來了。abstract class和interface是Java語言中的兩種定義抽象類的方式,它們之間有很大的類似性。可是對於它們的選擇卻又每每反映出對於問題領域中的概念本質的理解、對於設計意圖的反映是否正確、合理,由於它們表現了概念間的不一樣的關係(雖然都可以實現需求的功能)。這其實也是語言的一種的慣用法。
本節咱們詳解講解了類繼承、抽象類、接口以及在java8中出現的新特性,同時轉載了一篇雖然說文章比較久遠可是思想沒變,我的認爲寫的很是好的文章關於抽象類和接口的區別所在,但願帶給如我同樣的初學者更深層次的思考。