介面是什麼,抽象類又是什麼,有了抽象類,我們為什麼還要有介面呢? 抽象類 抽象類的概念: 抽象類是相對於普通類而言的,普通類是一個完善的功能類,可以直接產生實例化對象,並且在普通類中可以包含有構造方法、普通方法、static方法、常量和變數等內容。而抽象類是指在普通類的結構裡面增加抽象方法的組成部分 ...
介面是什麼,抽象類又是什麼,有了抽象類,我們為什麼還要有介面呢?
抽象類
抽象類的概念:
抽象類是相對於普通類而言的,普通類是一個完善的功能類,可以直接產生實例化對象,並且在普通類中可以包含有構造方法、普通方法、static方法、常量和變數等內容。而抽象類是指在普通類的結構裡面增加抽象方法的組成部分,或者直接將類聲明為抽象類型;
同樣抽象方法是相對於普通方法而言的,在所有的普通方法上面都會有一個“{}”,這個表示方法體,有方法體的方法一定可以被對象直接使用。而抽象方法,是指沒有方法體的方法,同時抽象方法還必須使用關鍵字abstract做修飾。而擁有抽象方法的類就是抽象類,抽象類要使用abstract關鍵字聲明。
下麵是一個簡單的抽象類:
package abstractdemo; //抽象類 public abstract class AbstractDemo { //抽象方法,沒有方法體 public abstract void test(); public void test1(){ System.out.println("普通方法"); } public static void main(String[] args) { Class<AbstractDemo> c = AbstractDemo.class; System.out.println(c.getName()); } }
抽象類的實例:
package abstractdemo; public abstract class AbstractTest { public static void main(String[] args) { AbstractDemo ad = new AbstractDemo() { @Override public void test() { System.out.println("實例抽象類要重寫抽象類中所有的抽象方法!"); } }; ad.test(); System.out.println(ad.getClass()); } }
從上可知,抽象類是無法直接進行實例化操作。為什麼不能直接實例化呢?當一個類實例化之後,就意味著這個對象可以調用類中的屬性或者方法了,但在抽象類里存在抽象方法,而抽象方法沒有方法體,沒有方法體就無法進行調用。既然無法進行方法調用的話,又怎麼去產生實例化對象呢。所以實例化抽象類時一定會先讓你重寫抽象類中的抽象方法;如果一個抽象類中沒有抽象方法,那麼這個類不能被實例化,會在實例化時編譯報錯;
抽象類的使用原則如下:
(1)抽象方法必須為public或者protected(因為如果為private,則不能被子類繼承,子類便無法實現該方法),預設情況下預設為public;
(2)抽象類不能直接實例化,需要依靠子類採用向上轉型的方式處理;
(3)抽象類必須有子類,使用extends繼承,一個子類只能繼承一個抽象類;
(4)子類(如果不是抽象類)則必須覆寫抽象類之中的全部抽象方法(如果子類沒有實現父類的抽象方法,那麼子類也是抽象類)
下麵看一段代碼:
package abstractdemo; //抽象類 public abstract class AbstractDemo { //抽象方法,沒有方法體 public abstract void test(); public void test1(){ System.out.println("普通方法"); } public static void main(String[] args) { Class<AbstractDemo> c = AbstractDemo.class; System.out.println(c.getName()); } } package abstractdemo; public class Test extends AbstractDemo{ @Override public void test() { System.out.println("繼承抽象類,一定要重寫抽象類中的抽象方法,如果不重寫,那麼該類也是抽象類!"); } } package abstractdemo; public class Demo { public static void main(String[] args) { AbstractDemo ad = new Test(); ad.test(); } }
現在就可以清楚的發現:
(1)抽象類繼承子類裡面有明確的方法覆寫要求,而普通類可以有選擇性的來決定是否需要覆寫;
(2)抽象類實際上就比普通類多了一些抽象方法而已,其他組成部分和普通類完全一樣;
(3)普通類對象可以直接實例化,但抽象類的對象必須經過向上轉型之後才可以得到。
雖然一個類的子類可以去繼承任意的一個普通類,可是從開發的實際要求來講,普通類儘量不要去繼承另外一個普通類,而是去繼承抽象類。
抽象類的使用限制:
1.由於抽象類里會存在一些屬性,那麼抽象類中一定存在構造方法,其存在目的是為了屬性的初始化。抽象類中是有構造函數的,所以子類繼承抽象類,構造方法的調用順序仍然是先父類構造函數,然後子類構造函數
2.抽象類不可以修飾為final,因為抽象類有子類,而final修飾的類不能被繼承;
3.外部抽象類不能被修飾為static,外部抽象類不允許使用static聲明,而內部的抽象類運行使用static聲明。使用static聲明的內部抽象類相當於一個外部抽象類的成員,繼承的時候使用“外部類.內部類”的形式表示類名稱。
package com.wz.abstractdemo; static abstract class A{//定義一個抽象類 public abstract void print(); } class B extends A{ public void print(){ System.out.println("**********"); } } public class TestDemo { public static void main(String[] args) { A a = new B();//向上轉型 a.print(); } }
執行結果
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
Illegal modifier for the class A; only public, abstract & final are permitted
at com.wz.abstractdemo.A.<init>(TestDemo.java:3)
at com.wz.abstractdemo.B.<init>(TestDemo.java:9)
at com.wz.abstractdemo.TestDemo.main(TestDemo.java:18)
再看一個關於內部抽象類:
package com.wz.abstractdemo; abstract class A{//定義一個抽象類 static abstract class B{//static定義的內部類屬於外部類 public abstract void print(); } } class C extends A.B{ public void print(){ System.out.println("**********"); } } public class TestDemo { public static void main(String[] args) { A.B ab = new C();//向上轉型 ab.print(); } }
執行結果:
**********
4.任何時候,如果要執行類中的static方法的時候,都可以在沒有對象的情況下直接調用,對於抽象類也一樣。但是修飾為abstract的抽象方法,不能修飾為static,沒有意義;
5.有時候由於抽象類中只需要一個特定的系統子類操作,所以可以忽略掉外部子類。這樣的設計在系統類庫中會比較常見,目的是對用戶隱藏不需要知道的子類。
範例如下:
package com.wz.abstractdemo; abstract class A{//定義一個抽象類 public abstract void print(); private static class B extends A{//內部抽象類子類 public void print(){//覆寫抽象類的方法 System.out.println("Hello World !"); } } //這個方法不受實例化對象的控制 public static A getInstance(){ return new B(); } } public class TestDemo { public static void main(String[] args) { //此時取得抽象類對象的時候完全不需要知道B類這個子類的存在 A a = A.getInstance(); a.print(); } }
運行結果:
Hello World !
抽象類的應用——模板設計模式
例如,現在有三類事物:
(1)機器人:充電,工作;
(2)人:吃飯,工作,睡覺;
(3)豬:進食,睡覺。
現要求實現一個程式,可以實現三種不同事物的行為。
先定義一個抽象行為類:
package com.wz.abstractdemo; public abstract class Action{ public static final int EAT = 1 ; public static final int SLEEP = 3 ; public static final int WORK = 5 ; public abstract void eat(); public abstract void sleep(); public abstract void work(); public void commond(int flags){ switch(flags){ case EAT: this.eat(); break; case SLEEP: this.sleep(); break; case WORK: this.work(); break; case EAT + SLEEP: this.eat(); this.sleep(); break; case SLEEP + WORK: this.sleep(); this.work(); break; default: break; } } }
定義一個機器人的類:
package com.wz.abstractdemo; public class Robot extends Action{ @Override public void eat() { System.out.println("機器人充電"); } @Override public void sleep() { } @Override public void work() { System.out.println("機器人工作"); } }
定義一個人的類:
package com.wz.abstractdemo; public class Human extends Action{ @Override public void eat() { System.out.println("人吃飯"); } @Override public void sleep() { System.out.println("人睡覺"); } @Override public void work() { System.out.println("人工作"); } }
定義一個豬的類:
package com.wz.abstractdemo; public class Pig extends Action{ @Override public void eat() { System.out.println("豬進食"); } @Override public void sleep() { System.out.println("豬睡覺"); } @Override public void work() { } }
測試主類:
package com.wz.abstractdemo; public class AbstractDemo { public static void main(String[] args) { fun(new Robot()); fun(new Human()); fun(new Pig()); } public static void fun(Action act){ act.commond(Action.EAT); act.commond(Action.SLEEP); act.commond(Action.WORK); } }
運行結果:
機器人充電
機器人工作
人吃飯
人睡覺
人工作
豬進食
豬睡覺
所有的子類如果要想正常的完成操作,必須按照指定的方法進行覆寫才可以,而這個時候抽象類所起的功能就是一個類定義模板的功能。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
介面
介面是一種比抽象類更加抽象的“類”。這裡給“類”加引號是我找不到更好的詞來表示,但是我們要明確一點就是,介面本身就不是類,從我們不能實例化一個介面就可以看出。如new Runnable();肯定是錯誤的,我們只能new它的實現類。
介面是用來建立類與類之間的協議,它所提供的只是一種形式,而沒有具體的實現。同時實現該介面的實現類必須要實現該介面的所有方法,通過使用implements關鍵字,他表示該類在遵循某個或某組特定的介面,同時也表示著“interface只是它的外貌,但是現在需要聲明它是如何工作的”。
介面是抽象類的延伸,Java了保證數據安全是不能多重繼承的,也就是說繼承只能存在一個父類,但是介面不同,一個類可以同時實現多個介面,不管這些介面之間有沒有關係,所以介面彌補了抽象類不能多重繼承的缺陷,但是推薦繼承和介面共同使用,因為這樣既可以保證數據安全性又可以實現多重繼承。
在使用介面過程中需要註意如下幾個問題:
1、1個Interface的所有方法訪問許可權自動被聲明為public。確切的說只能為public,當然你可以顯示的聲明為protected、private,但是編譯會出錯!
2、介面中可以定義“成員變數”,或者說是不可變的常量,因為介面中的“成員變數”會自動變為為public static final。可以通過類命名直接訪問:ImplementClass.name。
3、介面中不存在實現的方法。
4、實現介面的非抽象類必須要實現該介面的所有方法。抽象類可以不用實現。
5、不能使用new操作符實例化一個介面,但可以聲明一個介面變數,該變數必須引用(refer to)一個實現該介面的類的對象。可以使用 instanceof 檢查一個對象是否實現了某個特定的介面。例如:if(anObject instanceof Comparable){}。
6、在實現多介面的時候一定要避免方法名的重覆。
抽象類和介面的區別
在Java語言中,abstract class和interface是支持抽象類定義的兩種機制。正是由於這兩種機制的存在,才賦予了Java強大的面向對象能力。abstract class和interface之間在對於抽象類定義的支持方面具有很大的相似性,甚至可以相互替換,因此很多開發者在進行抽象類定義時對於abstract class和interface的選擇顯得比較隨意。其實,兩者之間還是有很大的區別的,對於它們的選擇甚至反映出對於問題領域本質的理解、對於設計意圖的理解是否正確、合理。
|
Abstract class |
Interface |
實例化 |
不能 |
不能 |
類 |
一種繼承關係,一個類只能使用一次繼承關係。可以通過繼承多個介面實現多重繼承 |
一個類可以實現多個interface |
數據成員 |
可有自己的 |
靜態的不能被修改即必須是static final,一般不在此定義 |
方法 |
可以私有的,非abstract方法,抽象方法必須實現 |
不可有私有的,預設是public,abstract 類型 |
變數 |
可有私有的,預設是friendly 型,其值可以在子類中重新定義,也可以重新賦值 |
不可有私有的,預設是public static final 型,且必須給其初值,實現類中不能重新定義,不能改變其值。 |
設計理念 |
表示的是“is-a”關係 |
表示的是“like-a”關係 |
實現 |
需要繼承,要用extends |
要用implements |
abstract class和interface在Java語言中都是用來進行抽象類(本文中的抽象類並非從abstract class翻譯而來,它表示的是一個抽象體,而abstract class為Java語言中用於定義抽象類的一種方法)定義的,那麼什麼是抽象類,使用抽象類能為我們帶來什麼好處呢?
聲明方法的存在而不去實現它的類被叫做抽象類(abstract class),它用於要創建一個體現某些基本行為的類,併為該類聲明方法,但不能在該類中實現該類的情況。不能創建abstract 類的實例。然而可以創建一個變數,其類型是一個抽象類,並讓它指向具體子類的一個實例。不能有抽象構造函數或抽象靜態方法。Abstract 類的子類為它們父類中的所有抽象方法提供實現,否則它們也是抽象類。取而代之,在子類中實現該方法。知道其行為的其它類可以在類中實現這些方法。
介面(interface)是抽象類的變體。在介面中,所有方法都是抽象的。多繼承性可通過實現這樣的介面而獲得。介面中的所有方法都是抽象的,沒有一個有程式體。介面只可以定義static final成員變數。介面的實現與子類相似,除了該實現類不能從介面定義中繼承行為。當類實現特殊介面時,它定義(即將程式體給予)所有這種介面的方法。 然後,它可以在實現了該介面的類的任何對象上調用介面的方法。由於有抽象類,它允許使用介面名作為引用變數的類型。通常的動態聯編將生效。引用可以轉換到 介面類型或從介面類型轉換,instanceof 運算符可以用來決定某對象的類是否實現了介面。
介面可以繼承介面。抽象類可以實現(implements)介面,抽象類是可以繼承實體類,但前提是實體類必須有明確的構造函數。介面更關註“能實現什麼功能”,而不管“怎麼實現的”。
1.相同點
A. 兩者都是抽象類,都不能實例化。
B. interface實現類及abstrct class的子類都必須要實現已經聲明的抽象方法。
2. 不同點
A. interface需要實現,要用implements,而abstract class需要繼承,要用extends。
B. 一個類可以實現多個interface,但一個類只能繼承一個abstract class。
C. interface強調特定功能的實現,而abstract class強調所屬關係。
D. 儘管interface實現類及abstrct class的子類都必須要實現相應的抽象方法,但實現的形式不同。interface中的每一個方法都是抽象方法,都只是聲明的
(declaration, 沒有方法體),實現類必須要實現。而abstract class的子類可以有選擇地實現。
這個選擇有兩點含義:
一是Abastract class中並非所有的方法都是抽象的,只有那些冠有abstract的方法才是抽象的,子類必須實現。那些沒有abstract的方法,在Abstrct class中必須定義方法體。
二是abstract class的子類在繼承它時,對非抽象方法既可以直接繼承,也可以覆蓋;而對抽象方法,可以選擇實現,也可以通過再次聲明其方法為抽象的方式,無需實現,留給其子類來實現,但此類必須也聲明為抽象類。既是抽象類,當然也不能實例化。
E. abstract class是interface與Class的中介。
interface是完全抽象的,只能聲明方法,而且只能聲明public的方法,不能聲明private及protected的方法,不能定義方法體,也 不能聲明實例變數。然而,interface卻可以聲明常量變數,並且在JDK中不難找出這種例子。但將常量變數放在interface中違背了其作為接 口的作用而存在的宗旨,也混淆了interface與類的不同價值。如果的確需要,可以將其放在相應的abstract class或Class中。
abstract class在interface及Class中起到了承上啟下的作用。一方面,abstract class是抽象的,可以聲明抽象方法,以規範子類必須實現的功能;另一方面,它又可以定義預設的方法體,供子類直接使用或覆蓋。另外,它還可以定義自己 的實例變數,以供子類通過繼承來使用。
interface的應用場合
A. 類與類之前需要特定的介面進行協調,而不在乎其如何實現。
B. 作為能夠實現特定功能的標識存在,也可以是什麼介面方法都沒有的純粹標識。
C. 需要將一組類視為單一的類,而調用者只通過介面來與這組類發生聯繫。
D. 需要實現特定的多項功能,而這些功能之間可能完全沒有任何聯繫。
abstract class的應用場合
一句話,在既需要統一的介面,又需要實例變數或預設的方法的情況下,就可以使用它。最常見的有:
A. 定義了一組介面,但又不想強迫每個實現類都必須實現所有的介面。可以用abstract class定義一組方法體,甚至可以是空方法體,然後由子類選擇自己所感興趣的方法來覆蓋。
B. 某些場合下,只靠純粹的介面不能滿足類與類之間的協調,還必需類中表示狀態的變數來區別不同的關係。abstract的中介作用可以很好地滿足這一點。
C. 規範了一組相互協調的方法,其中一些方法是共同的,與狀態無關的,可以共用的,無需子類分別實現;而另一些方法卻需要各個子類根據自己特定的狀態來實現特定的功能。
介面和抽象類的好處:
好像定義介面是提前做了個多餘的工作。下麵我給大家總結了4點關於JAVA中介面存在的意義:
1、重要性:在Java語言中, abstract class 和interface 是支持抽象類定義的兩種機制。正是由於這兩種機制的存在,才賦予了Java強大的 面向對象能力。
2、簡單、規範性:如果一個項目比較龐大,那麼就需要一個能理清所有業務的架構師來定義一些主要的介面,這些介面不僅告訴開發人員你需要實現那些業務,而且也將命名規範限制住了(防止一些開發人員隨便命名導致別的程式員無法看明白)。
3、維護、拓展性:比如你要做一個畫板程式,其中裡面有一個面板類,主要負責繪畫功能,然後你就這樣定義了這個類。
可是在不久將來,你突然發現這個類滿足不了你了,然後你又要重新設計這個類,更糟糕是你可能要放棄這個類,那麼其他地方可能有引用他,這樣修改起來很麻煩。
如果你一開始定義一個介面,把繪製功能放在介面里,然後定義類時實現這個介面,然後你只要用這個介面去引用實現它的類就行了,以後要換的話只不過是引用另一個類而已,這樣就達到維護、拓展的方便性。
4、安全、嚴密性:介面是實現軟體松耦合的重要手段,它描敘了系統對外的所有服務,而不涉及任何具體的實現細節。這樣就比較安全、嚴密一些(一般軟體服務商考慮的比較多)。
儘管抽象類和介面之間存在較大的相同點,甚至有時候還可以互換,但這樣並不能彌補他們之間的差異之處。下麵將從語法層次和設計層次兩個方面對抽象類和介面進行闡述。
語法層次
在語法層次,java語言對於抽象類和介面分別給出了不同的定義。下麵已Demo類來說明他們之間的不同之處。
使用抽象類來實現:
public abstract class Demo { abstract void method1(); void method2(){ //實現 } }
使用介面來實現
interface Demo { void method1(); void method2(); }
抽象類方式中,抽象類可以擁有任意範圍的成員數據,同時也可以擁有自己的非抽象方法,但是介面方式中,它僅能夠有靜態、不能修改的成員數據(但是我們一般是不會在介面中使用成員數據),同時它所有的方法都必須是抽象的。在某種程度上來說,介面是抽象類的特殊化。
對子類而言,它只能繼承一個抽象類(這是java為了數據安全而考慮的),但是卻可以實現多個介面。
設計層次
上面只是從語法層次和編程角度來區分它們之間的關係,這些都是低層次的,要真正使用好抽象類和介面,我們就必須要從較高層次來區分了。只有從設計理念的角度才能看出它們的本質所在。一般來說他們存在如下三個不同點:
1、 抽象層次不同。抽象類是對類抽象,而介面是對行為的抽象。抽象類是對整個類整體進行抽象,包括屬性、行為,但是介面卻是對類局部(行為)進行抽象。
2、 跨域不同。抽象類所跨域的是具有相似特點的類,而介面卻可以跨域不同的類。我們知道抽象類是從子類中發現公共部分,然後泛化成抽象類,子類繼承該父類即可,但是介面不同。實現它的子類可以不存在任何關係,共同之處。例如貓、狗可以抽象成一個動物類抽象類,具備叫的方法。鳥、飛機可以實現飛Fly介面,具備飛的行為,這裡我們總不能將鳥、飛機共用一個父類吧!所以說抽象類所體現的是一種繼承關係,要想使得繼承關係合理,父類和派生類之間必須存在"is-a" 關係,即父類和派生類在概念本質上應該是相同的。對於介面則不然,並不要求介面的實現者和介面定義在概念本質上是一致的, 僅僅是實現了介面定義的契約而已。
3、 設計層次不同。對於抽象類而言,它是自下而上來設計的,我們要先知道子類才能抽象出父類,而介面則不同,它根本就不需要知道子類的存在,只需要定義一個規則即可,至於什麼子類、什麼時候怎麼實現它一概不知。比如我們只有一個貓類在這裡,如果你這是就抽象成一個動物類,是不是設計有點兒過度?我們起碼要有兩個動物類,貓、狗在這裡,我們在抽象他們的共同點形成動物抽象類吧!所以說抽象類往往都是通過重構而來的!但是介面就不同,比如說飛,我們根本就不知道會有什麼東西來實現這個飛介面,怎麼實現也不得而知,我們要做的就是事前定義好飛的行為介面。所以說抽象類是自底向上抽象而來的,介面是自頂向下設計出來的。
我們有一個Door的抽象概念,它具備兩個行為open()和close(),此時我們可以定義通過抽象類和介面來定義這個抽象概念:
抽象類:
abstract class Door{ abstract void open(); abstract void close(); }
介面
interface Door{ void open(); void close(); }
至於其他的具體類可以通過使用extends使用抽象類方式定義Door或者Implements使用介面方式定義Door,這裡發現兩者並沒有什麼很大的差異。
但是現在如果我們需要門具有報警的功能,那麼該如何實現呢?
解決方案一:給Door增加一個報警方法:clarm();
abstract class Door{ abstract void open(); abstract void close(); abstract void alarm(); }
或者
interface Door{ void open(); void close(); void alarm(); }
這種方法違反了面向對象設計中的一個核心原則 ISP (Interface Segregation Principle)(參見:http://www.cnblogs.com/cy163/archive/2009/07/26/1531457.html),在Door的定義中把Door概念本身固有的行為方法和另外一個概念"報警器"的行為方 法混在了一起。這樣引起的一個問題是那些僅僅依賴於Door這個概念的模塊會因為"報警器"這個概念的改變而改變,反之依然。
解決方案二
既然open()、close()和alarm()屬於兩個不同的概念,那麼我們依據ISP原則將它們分開定義在兩個代表兩個不同概念的抽象類裡面,定義的方式有三種:
1、兩個都使用抽象類來定義。
2、兩個都使用介面來定義。
3、一個使用抽象類定義,一個是用介面定義。
由於java不支持多繼承所以第一種是不可行的。後面兩種都是可行的,但是選擇何種就反映了你對問題域本質的理解。
如果選擇第二種都是介面來定義,那麼就反映了兩個問題:1、我們可能沒有理解清楚問題域,AlarmDoor在概念本質上到底是門還報警器。2、如果我們對問題域的理解沒有問題,比如我們在分析時確定了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(){} }
這種實現方式基本上能夠明確的反映出我們對於問題領域的理解,正確的揭示我們的設計意圖。其實抽象類表示的是"is-a"關係,介面表示的是"like-a"關係,大家在選擇時可以作為一個依據,當然這是建立在對問題領域的理解上的,比如:如果我們認為AlarmDoor在概念本質上是報警器,同時又具有Door的功能,那麼上述的定義方式就要反過來了。
批註:
|
總結
1、 抽象類在java語言中所表示的是一種繼承關係,一個子類只能存在一個父類,但是可以存在多個介面。
2、 在抽象類中可以擁有自己的成員變數和非抽象類方法,但是介面中只能存在靜態的不可變的成員數據(不過一般都不在介面中定義成員數據),而且它的所有方法都是抽象的。
3、抽象類和介面所反映的設計理念是不同的,抽象類所代表的是“is-a”的關係,而介面所代表的是“like-a”的關係。
抽象類和介面是java語言中兩種不同的抽象概念,他們的存在對多態提供了非常好的支持,雖然他們之間存在很大的相似性。但是對於他們的選擇往往反應了您對問題域的理解。只有對問題域的本質有良好的理解,才能做出正確、合理的設計。