java 抽象類別與介面的定義
Jvm :
抽象規範;
一個具體實現
一個啟動並執行虛擬機器執行個體
裝載器: 啟動類裝載器,自訂裝載器(繼承 java.lang.ClassLoader )
解析的內型資訊放入方法區,對象放入堆,一個新線程,有自己 java 棧放中間資料, pc 計數器。
介面與抽象類別深入
java 沒有多重繼承,意味一個類只能繼承一個父類所以絕對必要的時候,才用繼承
所以表示相同行為的方法都應該聲明為一個介面的的形式,並使用不同的實作類別對其進行實現。
缺點:每一個實作類別都需要顯示的實現聲明中所有的方法。即便有些時候 , 這些方法 代表著不變的功能部分 ,並且在所有類中都有完全相同的實現,這樣導致冗餘代碼。
Public abstract class Employee{
String name ;
String id;
Public Employee (String empName, String empId){
name=empName;
id = empId;
}
public String getName()
{
return name;
}
Public String getID(){
return id;
}
}
Public void save(){
fileUtil futil=new FileUtil();
futil.writeToFile(“emp.txt”,this,toString,true,true);
}
Public abstract String computeCompensation();
}
不同的頭銜的僱員,計算薪水的方式不同。這樣一種操作多種方法實現。
public class consultant extends Employee{
public String computeCompensation(){
Return(“consultant salary ”);
}
Public consultant(String empname,String empid){
super(empname,empid);
}
}
Pulbic class salesRep exends Employee{
Public String computeCompensation(){
Retrun(“salesRep salary”);
}
Public salesRep(String empName,String empId){
super(empName,empId);
}
}
用抽象類別來實現
用介面來實現
用介面來實現的話,實作類別中都要給出
Save ,getid,getname 等方法。這樣導致了冗餘代碼。
介面和抽象類別在設計層面上的區別
“一個類一次只能繼續一個抽象類別,但可以實現若干個介面”。
首先注重這個要點的用詞:“繼續---類,實現---介面”,抽象父類和他的子類在體現出的是一種“繼續”關係,要想使得“繼續”關係合理,抽象父類和子類之間應該存在著“ is - a“ 關係,即父類和子類在概念本質上應該是相同的。
而 對於介面來說則不然,介面是拿來“實現”的,並不要求介面的實作類別和介面定義在概念本質上是一致的,因為眾所周知,介面只是定義了一組方法的架構,它的本 質是行為特徵的集合,規範。一個類實現了介面,僅僅是實現了介面所定義的規範而已,實現了介面的類並不“屬於”介面!這時,應該說介面和實現了介面的子類 的關係是一種“like - a” 的關係。
而且,一個類是可以實現多個介面的,在這種情況下,實現多個介面的類體現出的是“ like - a , like - b, like - c” 的關係。在單繼續的 Java 中,抽象類別是做不到這點的。假設即使能這麼做,假如一個類“ is - a, is - b , is - c” ,這和我們對世界的理解也不太一致。因為我們看待事物時,習慣把事物歸屬一類,但同時可以具有其他類的特徵。
考慮這樣一個例子,假設我們在為一個生產廠家開發手機軟體,問題領域中有一個關於手機的抽象概念,該手機具一些動作如開機,關機等,此時我們可以通過介面或者抽象類別來定義一個表示該抽象概念的類型,定義方式分別如下所示:
使用介面方式定義手機:
Interface Mobile_phone{
Void pen () ;
Void close () ;
}
使用抽象類別方式定義手機:
Abstract class Mobile_phone{
Abstract void open ();
Abstract void close ();
}
而具體的手機類型可以繼續抽象類別方式定義,或者使用實現介面的方式定義。看起來似乎使用介面和抽象類別沒有大的區別。
假如現在要求手機要整合有信用卡的功能。我們應該如何設計針對該例子的類結構呢?
信用卡的一些準系統有,電子錢包,消費等,這些功能和手機的開機關機功能屬於兩個不同的概念,根據 ISP (InterfaceSegregationPrinciple )原則應該把它們分別定義在代表這兩個概念的抽象表示中。這時“信用卡”這個抽象概念的定義可能是這兩種情況:
Interface Creditcard {
Void e_wallet () ; // 電子錢包
Void consume () ; // 消費
}
或
Abstract class Creditcard{
Abstract void e_wallet () ;
Abstract void consume ();
}
這時手機和銀行卡這兩個概念的定義方式就有了四種可能的組合,如下表:
手機
信用卡
方案 A
手機 定義為抽象類別
信用卡 定義為抽象類別
方案 B
手機定義為抽象類別
信用卡定義為介面
方案 C
手機定義為介面
信用卡定義為抽象類別
方案 D
手機定義為介面
信用卡定義為介面
方案 A 可以馬上排除,因為 Java 不支援多繼續, 手機要整合有信用卡 實作類別這無法同時繼續這兩個抽象類別。在這裡可以看到抽象類別在通過概念的組合來擴充功能的時候是不方便的。
研究一下方案 C ,如本文所述,這時實現這兩個概念的類和這兩個概念之間的關係就是:“ like” 手機,“is” 信用卡。顯然這和我們對問題領域的理解不符。因為“帶信用卡功能的手機” 在概念本質上是手機,同時具有信用卡的功能 。所以這個方案是不合理的。除非我們是在為信用卡的製造商寫軟體,他們希望加入手機的功能。
而且,假如手機再擴充功能,如電子地圖,導航器—--等等,把這種擴充的功能概念象“信用卡”一樣定義為抽象類別的話,由於 Java 的單繼續機制,這是無法實現的。這道理和方案 A 一樣,把用來擴充功能的概念定義為抽象類別並不合適。
方案 B 應該是目前最合理的設計了,這時反映出的概念是“ is 手機, like 信用卡”。假如有擴充功能的話,可以再定義成介面,成為“ is 手機, like 信用卡 like 電子地圖---”,從而正確的反應我們面對的問題域。
那方案 D 是不是就不行呢?
相對方案 C 來說,方案 D 的設計沒有反映出“手機”是問題領域的本質的主體,使人有“到底我們在搞手機還是信用 卡還是別的什麼東西?”這個疑問。這個缺點是不容置疑的。但從另一方面來說,“手機”這個概念定義成介面,在軟體規模擴大的前提下,也許為以後其他的組件 的使用提供了方便。比如說,假設廠家又有一個“遙控器”的概念要我們設計,要把手機的功能設計進去,這時候“手機”假如是介面就方便了, implements 他就行。所以說,方案 D 是犧牲了概念的清楚性,得到了擴充性。
這裡得到的結論就是:假如只是在定義一組行為架構的話,抽象類別合適用來定義問題領域中的本質的抽象概念,介面合適用來定義擴充功能的抽象概念。
不能說這個結論就總結了抽象類別和介面的全部區別,這隻是問題的一小部分結論而已。再舉一個例子來說明他們的另外一方面的區別。
在剛剛這個例子中,“手機”,“信用卡”僅僅是一組抽象方法,也就是概念中含有的只是行為架構沒有實現,這時候定義成抽象類別或介面都有自己的道理。假如概念中已經含有了實現,這時候就把應該概念定義成抽象類別了。
比如一個” A 系列印表機”的抽象類別,由他定義不同類型的印表機,那一系列的印表機列印頁頭,頁尾的方案都是一樣的,但列印頁面主體比較複雜,各種具體型號的印表機的各有它們不同的列印方法,這時可以這麼設計:
方案一:按照印表機應該列印完整頁面的自然邏輯, body 抽象方法是印表機這個概念的一部分,設計為抽象類別:
Abstract class A_SeriesPrinter{
Abstract protected void PrintBody () ;
Public void OutReport () {
PrintHeader () ;
PrintBody () ;
PrintFooter () ;
}
Protected void Draw ( Stringstr ) {/ 實現的代碼 /}
Protected void PrintHeader () {Draw (“ Head“ ) ;/ 實現的代碼 /}
Protected void PrintFooter () {Draw (“ Footer“ ) ;/ 實現的代碼 /}
}
繼續抽象類別的代碼:
Class XXPrinter extends A_SeriesPrinter{
Protected void PrintBody () {/ 實現的代碼 /} // 這個一定要實現吧?
}
classYYPrinter extends A_SeriesPrinter{
protected void PrintBody () {/ 實現的代碼 /}
}
運用的代碼:
XXPrinterxx = new XXPrinter () ;
Xx . OutReport () ;
YYPrinteryy = new YYPrinter () ;
Yy . OutReport () ;
顯然這個方案是簡單而清楚的。
方案二:為了擴充性,硬把 body 抽象方法取出來成為一個介面,代碼如下:
Abstract class A_SeriesPrinter{ // 思考一下,還用 abstract 嗎?
Protected void Draw ( Stringstr ) {/ 實現的代碼 /}
Protected void PrintHeader () {Draw (“ Head“ ) ;/ 實現的代碼 /}
Protected void PrintFooter () {Draw (“ Footer“ ) ;/ 實現的代碼 /}
}
Interface Body{
void PrintBody () ;// 多了一個 Body 介面的概念
}
在這裡先解決一個問題,假如 Printer 去掉了 PrintBody 抽象方法,都是實現了的方法,是不是就應該把它定義為普通的類呢?
答案是否定的,設計一個抽象概念為抽象類別的意義,不是因為它含有抽象方法,而主要因為是他表示的概念不應該被執行個體化,即使它裡頭的方法全部是實現了的,想讓子類繼續的代碼。在上面這個例子中,” A 系列印表機”這個概念,是不應該有執行個體的, 有執行個體的應該是具體型號的印表機 。所以,即便是全部是實現了的方法,方案二中的A_SeriesPrinter 還是定義成抽象類別更好。
好,繼續看繼續類並實現介面的代碼:
Class XXPrinter extends A_SeriesPrinter implement Body {
Public void PrintBody () {;/ 實現的代碼 /}
Public void OutReport () {//OutReport ()被迫移到了實作類別
PrintHeader () ;
PrintBody () ;
PrintFooter () ;
}
}
Class YYPrinter extends A_SeriesPrinter implement sBody{
Public void PrintBody () {;/ 實現的代碼 /}
Public void OutReport () {//OutReport ()被迫移到了實作類別
PrintHeader () ;
PrintBody () ;
PrintFooter () ;
}
}
運用的代碼:
XXPrinter xx = new XXPrinter () ;
Xx . OutReport () ;
YYPrinter yy = new YYPrinter () ;
Yy . OutReport () ;
這樣做會顯得很希奇和複雜: class XXPrinter extends Printer implements Body ?似 乎列印 Body 竟然是印表機的附加功能?還無故的多出了一個 Body 介面的概念。而且, OutReport ()被迫移到了各個實作類別,代碼變長而且複雜了。
所以這時抽象類別是最好的選擇。除非有業務要求需要把 Body 的列印從印表機分離出來。套到別的概念中去。這時才有考慮使它成為介面的可能,但再次提醒大家,代碼會變得複雜。
追溯問題出現的源頭,是因為 PrintBody ()這個抽象方法和印表機這個概念結合的太緊密了,它本身就是印表機功能的不可缺少的 一部分。貪圖介面文法上的靈活性,盲目的追求擴充性開放性,而不顧對問題領域的理解而建模,只要某一個概念中含有的行為架構都分離出來搞成介面,就會有一 系列的編碼上和理解上的麻煩,反而增加了代碼的複雜性,自討苦吃。
然而,即使在使用抽象類別的場合,也不要忽視通過介面定義行為模型的原則。假如依靠於抽象類別來定義行為,往往導致過於複雜的繼承關係, 而通過介面定義行為能夠更有效地分離行為與實現,為代碼的維護和修改帶來方便。
比如我擴充 A_SeriesPrinter 類,在列印後加個日誌資訊,如 viodoutLog ()方法什麼的,那麼我就不應該把它定義成 A_SeriesPrinter 類的抽象方法了,而是日誌介面了。因為“日誌”這概念不屬於印表機的專有範疇。這樣以後其他模組用到關於日誌的操作規範時可以方便地用到這個日誌介面。
所以,關鍵在於能否出色地結合業務要求對問題域進行理解分析。假如你沒有做好這點,你就不能建立合理的模型 。
總結:
1 介面能向上轉型多個基本型別。
2 介面,介面讓用戶端程式員無法產生對象,確保這隻是個介面,抽象類別而無實體
3 你設計基本類 base class 時候,不帶任何函數定義或成員變數時候,優先考慮 interface,( 如果必須帶有函數定義或成員變數時候,改用 abstract class)
4 . 為了消除不變功能部分相同實現,避免代碼冗餘,應用抽象類別
5. 多態實現中 , is- a 和 like-a 關係
Is-a 是一種完全替換 base class 方法 ( 覆寫 overring ) ,重載( overloader )功能,可以用interface 來實現說介面和實現了介面的子類的關係是一種“ like - a” 多態。
看java用法
從設計理念層面看 abstract class 和 interface
上面主要從文法定義和編程的角度論述了abstract class和interface的區 別,這些層面的區別是比較低層次的、非本質的。本小節將從另一個層面:abstract class和interface所反映出的設計理念,來分析一下二者的區別。作者認為,從這個層面進行分析才能理解二者概念的本質所在。
前面已經提到過,abstract class在Java語言中體現了一種繼承關係,要想使得 繼承關係合理,父類和衍生類別之間必須存在"is-a"關係,即父類和衍生類別在概念本質上應該是相同的。對於interface來說則不然,並不要求interface的實現者和interface定義在概念本質上是一致的, 僅僅是實現了interface定義的契約而已。為了使論述便於理解,下面將通過一個簡單的執行個體進行說明。
考慮這樣一個例子,假設在我們的問題領域中有一個關於Door的抽象概念,該Door具有執行兩個動作open和close,此時我們可以通過abstract class或者interface來定義一個表示該抽象概念的類型,定義方式分別如下所示
介面定義
關於java的介面定義方式,以下三種情況下可以採用介面定義方式:
1. 介面中聲明的變數全部為final 和static類型的,並且這個介面的作用在於定義一些值不能改變的變數。
舉個例子:
public interface ObjectConstants{
public static final String SPACE = new String(" ");
public static final char FORMFEED = 'f';
}
2. 介面中只定義可供實現的抽象方法
EventListener.java
public interface EventListener {
public void handleEvent(Event evt);
}
Runnable.java
package java.lang;
public interface Runnable {
public abstract void run();
}
3. 還有一種方式是上述兩種方式的組合,如非必要一般會將這樣一個介面定義拆分成兩個介面定義
抽象類別的定義
1. 如果一個類包含一個介面但是不完全實現介面定義的方法,那麼該類必須定義成abstract型
例如InputStream.java類的定義方式:
package java.io;
public abstract class InputStream implements Closeable {
// SKIP_BUFFER_SIZE is used to determine the size of skipBuffer
private static final int SKIP_BUFFER_SIZE = 2048;
// skipBuffer is initialized in skip(long), if needed.
private static byte[] skipBuffer;
public abstract int read() throws IOException;
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = read();
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
if (b != null) {
b[off + i] = (byte)c;
}
}
} catch (IOException ee) {
}
return i;
}
public long skip(long n) throws IOException {
long remaining = n;
int nr;
if (skipBuffer == null)
skipBuffer = new byte[SKIP_BUFFER_SIZE];
byte[] localSkipBuffer = skipBuffer;
if (n <= 0) {
return 0;
}
while (remaining > 0) {
nr = read(localSkipBuffer, 0,
(int) Math.min(SKIP_BUFFER_SIZE, remaining));
if (nr < 0) {
break;
}
remaining -= nr;
}
return n - remaining;
}
public int available() throws IOException {
return 0;
}
public void close() throws IOException {}
public synchronized void mark(int readlimit) {}
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
public boolean markSupported() {
return false;
}
}
2. 抽象類別的方法體中只定義抽象的方法,例如AbstractMethodError.java
package java.lang;
public class AbstractMethodError extends IncompatibleClassChangeError {
public AbstractMethodError() {
super();}
public AbstractMethodError(String s) {
super(s); }
}