java溫故知新——抽象類與接口

 

  1、 抽象類和接口簡介java

  1.1 抽象類安全

  1.1.1 一個小案例ide

  咱們先來看這樣一個事例:世界上有許多的動物,每一種動物都要吃,移動(走?飛?)等等。如今讓你用java語言描述一下這個案例。spa

  你會說:很簡單啊,我但是學過繼承的人,一個小繼承就能解決問題。設計

  

// 父類

  public class Animal {

  public void move(){

  System.out.println("i an move");

  }

  }

  // 鳥 類

  public class Bird extends Animal {

  @Override

  public void move() {

  System.out.println("i can fly");

  }

  }

  // 狗 類

  public class Dog extends Animal {

  @Override

  public void move() {

  System.out.println("i can run");

  }

  }

  //......more

 

  看起來,你完成的不錯。code

  可是,我想問你一個問題: new Animal().move()這段代碼描述了一個什麼現實情景?對象

  」建立了一個動物,而後讓這個動物移動「,你可能會這麼回答我。可是,你難道沒有發現問題麼?現實世界裏,有叫作【動物】的生物麼?你見過這個叫作【動物】的生物移動麼?blog

  動物,是對生物的一種統稱,狗是動物,鳥也是動物。可是【動物】自己是一個抽象的概念,你在現實世界中,並無見過一種叫作【動物】生物吧?繼承

  你應該明白了,咱們能夠new一個Bird,new一個Dog,由於它們是實實在在的對象,可是咱們不該該new出一個Animal來,由於動物是一個抽象的概念,實際上它並不存在。接口

  事實上,Animal中的move()方法,也是有問題的不是麼?既然Animal不存在,那它怎麼會有真實存在的move()方法呢?

 

  1.1.2 抽象類和抽象方法

  在面向對象的概念中,全部的對象都是經過類來描繪的,可是反過來,並非全部的類都是用來描繪對象的,若是一個類中沒有包含足夠的信息來描繪一個具體的對象,這樣的類就是抽象類。

  就像咱們上面中的例子同樣。Dog和Bird能夠用一個普通類來描繪,可是Animal不能夠,Animal就應該是一個抽象類。

  在java中,被abstract修飾的類,叫作抽象類。抽象類中能夠定義抽象方法,也能夠定義普通方法。抽象類不能夠被實例化,只有被實體類繼承後,抽象類纔會有做用。

 

  抽象方法:

  被abstract修飾的方法叫作抽象方法,抽象方法沒有方法體,也就是說抽象方法沒有具體的實現。

  抽象方法必須定義在抽象類中。

  舉個例子: abstrac void move(); 這就是一個抽象方法。

  回到剛纔的問題,咱們如今利用抽象類來重構一下咱們的代碼:

  

// 父類

  public abstract class Animal {

  public abstract void move();//抽象方法

  }

  // 鳥 類

  public class Bird extends Animal {

  @Override

  public void move() {

  System.out.println("i can fly");

  }

  }

  // 狗 類

  public class Dog extends Animal {

  @Override

  public void move() {

  System.out.println("i can run");

  }

  }

  //......more

 

  由於抽象類不能夠實例化,因此如今就不用擔憂new Animal()這樣的狀況出現了。而且咱們將Animal類中的move方法也定義爲抽象方法,因此上面的全部問題,都迎刃而解了。

  抽象類就是用來被繼承的,脫離了繼承,抽象類就失去了價值。繼承了抽象類的子類,須要重寫抽象類中全部的抽象方法。

 

  在使用抽象類時須要注意幾點:

  抽象類不能被實例化,實例化的工做應該交由它的子類來完成,它只須要有一個引用便可。

  爲何抽象類不能實例化對象:

  抽象類的設計目的就是爲了處理相似於Animal這種沒法準確描述爲一個對象的狀況。因此不能夠實例化。

  抽象類中能夠定義抽象方法。抽象方法是沒有方法體的,必須被子類重寫後,該方法才能被正確調用。若是抽象類能實例化,那麼抽象方法也就能夠被調用,這顯然是不行的。

  子類必須重寫全部抽象方法。

  固然,不都重寫也能夠,可是這樣的話,子類也必須是抽象類。

  一個類裏只要有一個抽象方法,那麼這個類必須定義爲抽象類。

  抽象類中能夠包含具體的方法,固然也能夠不包含抽象方法。

  abstract不能與final並列修飾同一個類。

  abstract類就是爲了讓子類繼承,而final類不能被繼承。

  abstract 不能與private、static、final或native並列修飾同一個方法。

  抽象方法必須被子類重寫才能使用。

  1.2 接口

  java中的接口是一系列方法的聲明,是一些方法特徵的集合,一個接口只有方法的特徵沒有方法的實現,所以這些方法能夠在不一樣的地方被不一樣的類實現,而這些實現能夠具備不一樣的行爲。

  接口是一種比抽象類更加抽象的【類】。咱們要明確一點就是,接口自己就不是類。爲何說它更抽象呢?由於抽象類中還能夠定義普通方法,可是接口中只能寫抽象方法。

  接口是用來創建類與類之間的協議,它所提供的只是一種形式,而沒有具體的實現。接口中的全部方法默認都是public abstract的。

  接口是抽象類的延伸,java了保證數據安全是不能多重繼承的,也就是說繼承只能存在一個父類,可是接口不一樣,一個類能夠同時實現多個接口,無論這些接口之間有沒有關係,因此接口彌補了抽象類不能多重繼承的缺陷,可是推薦繼承和接口共同使用,由於這樣既能夠保證數據安全性又能夠實現多重繼承。

 

  在使用接口過程當中須要注意以下幾個問題:

  接口中的全部方法訪問權限自動被聲明爲public。確切的說只能爲public,固然你能夠顯示的聲明爲protected、private,可是編譯會出錯。

  接口中能夠定義變量,可是它會被強制變爲不可變的常量,由於接口中的「成員變量」會自動變爲爲public static final。能夠經過類命名直接訪問:ImplementClass.name。

  實現接口的非抽象類必需要實現該接口的全部方法。抽象類能夠不用實現。

  在實現多接口的時候必定要避免方法名的重複。

  由於一個類可能會實現多個接口,若是這兩個接口有名字相同的方法,會產生意想不到的問題。

  不能使用new操做符實例化一個接口,但能夠聲明一個接口變量,該變量必須引用(refer to)一個實現該接口的類的對象。可使用 instanceof 檢查一個對象是否實現了某個特定的接口。例如:if(anObject instanceof Comparable){}。

  值得一提的是,在java8中,接口裏也能夠定義默認方法:

  

public interface java8{

  //在接口裏定義默認方法

  default void test(){

  System.out.println("java 新特性");

  }

  }

 

  2、 抽象類和接口的區別

  2.1 語法定義層面

  在語法層面,Java語言對於abstract class和interface給出了不一樣的定義方式。

  

//抽象類

  public abstract class AbstractTest {

  abstract void method1();

  void method2(){

  //實現

  }

  }

  //接口

  interface InterfaceTest {

  void method1();

  void method2();

  }

 

  2.2 設計理念層面

  前面已經提到過,abstarct class在Java語言中體現了一種繼承關係,要想使得繼承關係合理,父類和派生類之間必須存在【is-a】關係,即父類和派生類在概念本質上應該是相同的。

  對於interface 來講則否則,並不要求interface的實現者和interface定義在概念本質上是一致的,僅僅是實現了interface定義的協議而已。

  咱們來看一個例子:假設在問題領域中有一個關於Door的抽象概念,該Door具備執行兩個動做open和close,此時咱們能夠經過abstract class或者interface來定義一個表示該抽象概念的類型,定義方式以下所示:

  

//抽象類

  abstract class Door{

  abstract void open();

  abstract void close();

  }

  //接口

  interface Door{

  void open();

  void close();

  }

 

  其餘具體的Door類型能夠extends使用abstract class方式定義的Door或者implements使 用interface方式定義的Door。看起來好像使用abstract class和interface沒有大的區別。

  若是如今要求Door還要具備報警的功能。咱們該如何設計針對該例子的類結構呢(在本例中,主要是展現abstract class和interface反映在設計理念上的區別,其餘方面無關的問題都簡化或者忽略)

     下面將羅列出可能的解決方案,並對這些方案進行分析。

 

  解決方案一:

  簡單的在Door的定義中增長一個alarm方法,以下:

  

abstract class Door{

  abstract void open();

  abstract void close();

  abstract void alarm();

  }

  或者

interface Door{

  void open();

  void close();

  void alarm();

  }

 

  這種方法違反了面向對象設計中的一個核心原則 ISP,在Door的定義中把Door概念自己固有的行爲方法和另一個概念"報警器"的行爲方法混在了一塊兒。這樣引發的一個問題是那些僅僅依賴於Door這個概念的模塊會由於"報警器"這個概念的改變而改變,反之依然。

  好比說,有一個普普統統的門,實現了Door接口,或者繼承了Door抽象類,它只須要開門和關門的行爲,可是當你像上面同樣修改了接口或者抽象類之後,那麼這個【普通門】也不得不具有了【報警】的功能,這顯然是不合理的。

  ISP(Interface Segregation Principle):面向對象的一個核心原則。它代表使用多個專門的接口比使用單一的總接口要好。

  一個類對另一個類的依賴性應當是創建在最小的接口上的。

  一個接口表明一個角色,不該當將不一樣的角色都交給一個接口。沒有關係的接口合併在一塊兒,造成一個臃腫的大接口,這是對角色和接口的污染。

 

  解決方案二

  既然open()、close()和alarm()屬於兩個不一樣的概念,那麼咱們依據ISP原則將它們分開定義在兩個表明兩個不一樣概念的抽象類裏面,定義的方式有三種:

  兩個都使用抽象類來定義。

  兩個都使用接口來定義。

  一個使用抽象類定義,一個是用接口定義。

  因爲java不支持多繼承因此第一種是不可行的。後面兩種都是可行的,可是選擇何種就反映了你對問題域本質的理解。

  若是選擇第二種都是接口來定義,那麼就反映了兩個問題:

  一、咱們可能沒有理解清楚問題域,AlarmDoor在概念本質上究竟是門還報警器。

  二、若是咱們對問題域的理解沒有問題,好比咱們在分析時肯定了AlarmDoor在本質上概念是一致的,那麼咱們在設計時就沒有正確的反映出咱們的設計意圖。由於你使用了兩個接口來進行定義,他們概念的定義並不可以反映上述含義。

 

  第三種,若是咱們對問題域的理解是這樣的:

  AlarmDoor本質上Door,但同時它也擁有報警的行爲功能,這個時候咱們使用第三種方案剛好能夠闡述咱們的設計意圖。

  AlarmDoor本質是門,因此對於這個概念咱們使用抽象類來定義,同時AlarmDoor具有報警功能,說明它可以完成報警概念中定義的行爲功能,因此alarm可使用接口來進行定義。以下:

  

abstract class Door{

  abstract void open();

  abstract void close();

  }

  interface Alarm{

  void alarm();

  }

  class AlarmDoor extends Door implements Alarm{

  void open(){}

  void close(){}

  void alarm(){}

  }

 

  這種實現方式基本上可以明確的反映出咱們對於問題領域的理解,正確的揭示咱們的設計意圖。

  其實abstract class表示的是【is-a】關係,interface表示的是【like- a】關係,你們在選擇時能夠做爲一個依據,固然這是創建在對問題領域的理解上的,好比:若是咱們認爲AlarmDoor在概念本質上是報警器,同時又具備 Door的功能,那麼上述的定義方式就要反過來了。

 

  3、 抽象類和接口的使用

  那咱們究竟如何選擇呢?究竟是使用抽象類,仍是使用接口?

  首先,咱們要明確一點:抽象類是爲了把相同的東西提取出來, 是爲了重用; 而接口的做用是提供程序裏面固化的契約, 是爲了下降偶合。抽象類表示的是,這個對象是什麼。接口表示的是,這個對象能作什麼。

  好比說,如今,我要用java描述一下學生和老師。學生和老師都有姓名,年齡,性別等,都會走路,吃飯;可是老師要授課,而學生要聽課,不一樣的老師授課的科目不一樣,不一樣專業的學生聽的課也不一樣。

  咱們能夠把老師和學生共有的屬性和方法提取出來,用抽象類表示:

public abstract class Person {

  String name;

  int age;

  String sex;

  abstract void eat();

  abstract void run();

  }

 

  老師會授課,不一樣的老師授課不一樣,咱們能夠定義一個接口:

  

public interface Teach {

  void teach(String className);

  }

 

  學生要上課,不一樣專業的學生上的科目不一樣,咱們也能夠定義爲接口:

  

public interface TakeClass {

  void takeClass(String className);

  }

 

  定義老師:

  

public class Teacher extends Person implements Teach {

  @Override

  public void teach(String className) {

  System.out.println("teach " + className);

  }

  @Override

  void eat() {

  System.out.println("teacher eat");

  }

  @Override

  void run() {

  System.out.println("teacher run");

  }

  }

 

  定義學生:

  

public class Student extends Person implements TakeClass {

  @Override

  public void takeClass(String className) {

  System.out.println("take class: " + className);

  }

  @Override

  void eat() {

  System.out.println("student eat");

  }

  @Override

  void run() {

  System.out.println("student run");

  }

  }

 

  這樣使用抽象類和接口,我以爲是一種很合理的方式。

  如今有不少討論和建議提倡用interface代替abstract類,二者從理論上能夠作通常性的混用,可是在實際應用中,他們仍是有必定區別的。抽象類通常做爲公共的父類爲子類的擴展提供基礎,這裏的擴展包括了屬性上和行爲上的。而接口通常來講不考慮屬性,只考慮方法,使得子類能夠自由的填補或者擴展接口所定義的方法。

  就像這個老師和學生的例子,抽象類提取了他們共有的屬性,他們各自有什麼屬性能夠交給子類去完成。有人可能會說,爲何不把eat 和 run 方法定義爲接口呢?這固然也是能夠的。可是吃和走,是人自身的一種行爲,它不像授課和上課這種是由於某種身份而特有的行爲,吃和走與人自身的屬性(姓名,年齡)都是【人】自己就有的,因此我以爲一塊兒放到抽象類裏更合適一些。固然,你單獨定義一個【人行爲】的接口從語法角度講也沒問題。

 

  4、 總結

  一、抽象類和接口都不能直接實例化,若是要實例化,抽象類變量必須指向實現全部抽象方法的子類對象,接口變量必須指向實現全部接口方法的類對象。

  二、抽象類要被子類繼承,接口要被類實現。

  三、接口只能作方法申明,抽象類中能夠作方法申明,也能夠作方法實現(不討論java8的狀況下)

  四、接口裏定義的變量只能是公共的靜態的常量,抽象類中的變量是普通變量。

  五、抽象類裏的抽象方法必須所有被子類所實現,若是子類不能所有實現父類抽象方法,那麼該子類只能是抽象類。一樣,一個實現接口的時候,如不能所有實現接口方法,那麼該類也只能爲抽象類。

  六、抽象方法只能申明,不能實現。不能寫成abstract void abc(){}。

  七、抽象類裏能夠沒有抽象方法

  八、若是一個類裏有抽象方法,那麼這個類只能是抽象類

  九、從實踐的角度來看,若是依賴於抽象類來定義行爲,每每致使過於複雜的繼承關係,而經過接口定義行爲可以更有效地分離行爲與實現,爲代碼的維護和修改帶來方便。

  十、選擇抽象類和接口的時候記得一句話:抽象類表示的是,這個對象是什麼。接口表示的是,這個對象能作什麼。

相關文章
相關標籤/搜索