Dashboard > JAVA SE > Home > Interface VS Abstract class
JAVA SE Log In   View a printable version of the current page.
Interface VS Abstract class
Added by zach14c, last edited by zach14c on Sep 21, 2006  (view change)
Labels: 
(None)

Interface 和 Abstract class 的差異,我想各位都有一定的瞭解,雖然在別的 forum 看過有人問起這個問題,我不打算在此討論兩者的細節。我打算只提及一些我個人對二者的感覺。

Interface 和 Class 兩者皆可引進 type 的觀念,前者純粹是定義了由一組介面所構成的規格,本身無法承載實做細節;後者本身也能帶來 type 的定義,同時還能附上實做的細節,abstract class 是一種 class(abstract class 甚至可以沒有任何 abstract method),同樣具備 class 所有的條件。這裡我強調的是後者(class)本身帶來 type(規格)這一件事。

為什麼要強調這一點呢?當你寫出如下的程式碼:

interface IX {
  public Object narrow();
}
 
public class XY implements IX {
  public static void main(String[] args)
  {
    XY obj = new XY();
    System.out.println(obj.narrow());
  }
 
  public Object narrow()
  {
    return this;
  }
}

XY implements IX 也定義了和 IX 所明列的操作(這是必然的,編譯器要求設計者做到這一點)。
基本上你操作 obj 所參考的物件是以 XY(type) 的觀點(規格)來操作,main method 裡沒有 IX interface 的參與,即使你沒有讓 XY implements IX,main method 所產生的 instructions 和上例所產生的是一模一樣,並不會因為 XY implements IX 而使得在 invoke 符合 IX 所明列的操作介面的那一點上編譯器"主動"把物件(XY instance)視為一種 IX 規格的東西。我對這一點的看法是,implements IX 只是通知編譯器在必要的時刻允許 VM 將 XY instance 當作是 IX 規格的東西,"XY implements IX" 表示出 XY 相容於 IX 規格(所以編譯器要求一定要明白實做出 IX 所有的介面,否則只能成為 abstract class)。儘管如此,narrow method 的確成為了 XY 的一個(操作)介面,我的意思是這並不是因為 XY implements IX 不得不實做出這樣的一個(操作)介面所帶來的結果,而是因為 XY 本身也定義(宣告)了 narrow 這樣一個(操作)介面而使得 XY 相容於 IX,回顧此段一開始所提及,即使 IX interface 根本不存在而 CX 沒有 implemets IX,main method 一樣能操作的好好(因為 instructions 完全沒變),不會有 runtime error,CX 能執行 narrow 操作不是拜 IX 之賜,純粹是 XY "自己"定義並實做了這個操作。可能乍聽之下有點意外,我的意思是:因為 XY 定義了 narrow (操作)介面,所以可以明列出 XY 是相容於 IX 這一點(implements IX)並在適當時刻把 XY object 當作 IX 規格之物,而不是反過來的因果關係,因為 implements IX,所以 XY 到定義並實做 narrow (操作)介面,雖然這樣說很合理,但我覺得這樣可能會帶來一些誤導。

現在出現了一個 XYZ 類別 extends XY 並 implements 其他的 interface but IX,XYZ 透過 extends 的繼承機制同時從 XY 竊取了規格(type)與實做(implementation),即便 XYZ 不作什麼就可以把 narrow 的實做碼變成自己的一部份(其實是可以向 XY 借用),因而 XYZ (一定)可以相容於 IX 規格,從這一點來看,可能會比較清楚我在上一段所強調的東西。現在我產生了 XYZ object 並執行其 narrow 操作:

XYZ obj = new XYZ();
// or XY obj = new XYZ();
obj.narrow();

上一段例子一樣,這幾行完全沒出現 IX 的影子,也沒受到 IX 帶來的影響,拿掉 IX 並不指明 XY 相容於 IX ,還是跑得好好的。(到這裡還是不清楚我的意思也無大礙,繼續下去就會慢慢清楚了)

照我所講的 "implements" 帶來 class 相容某個 interface 的資料與保證,可以在必要的時刻讓 VM 把 XY instance 當作 IX 規格之物來用,什麼是必要時刻呢?

XY obj = new XY();
System.out.println(((IX)obj).narrow());
/* or
 * IX obj = new XY();
 * System.out.println(obj.narrow());
 */

產生出來的 instructions 和 mark 掉的部分相同,(((IX)ob).narrow(); 此一 statement 並不會真的作 checkcast 的動作,類似 class 之間的 casting 的道理)只是語意上不大一樣,前者編譯器會以 XY 的規格來對待 new XY() 所建構的物件,"偶爾"臨時以 IX 規格來對待;後者則是直接以 IX 規格來對待新建構的物件。

正式進入主題了,這一次我想要比較什麼呢?基本上 class 和 inteface 本質就差異蠻大的,即使 abstract class 和 interface 之間還是差很多,所以我真正的焦點在於退化的 abstract class,一個只擁有(自己只定義) abstract method 的 pure abstract class(講 pure 也不太恰當,畢竟還是至少得繼承 java.lang.Object,就會有 concrete method)。一個 interface 可以改寫成 pure abstract class,用起來在多數情況下以 programmer 角度來看是相同的,除了 interface 可以隨時混搭到其他的 type hierarchy 裡,pure abstract class 不行,但是後者的資料承載比較強。如果現在有一份規格已訂出來了,你會以 pure abstract class 來訂製還是做成一個 interface,我會選擇做成 interface,基於彈性的考量,可能你也是吧!不過在看完下面的對抗之後,可能或多或少會影響你的抉擇。

對抗的是執行效率,因為以不同的觀點來看帶同一個物件並執行同一操作所使用的 instruction 不同,很明顯在效率上一定有差別。

XY obj = new XY();
IX x = obj;
obj.narrow(); // used instruction: invokevirtual
x.narrow();   // used instruction: invokeinterface

在實際測量前,我大概知道輸家是 invokeinterface,因為其需要從 stack 取出的 actual argument 總是比 invokevirtual 多一個(多一個用來指示要從 stack 取出的參數數量),花的時間很難不多一點。

我把測試用的相關類別 post 一份在這裡方便直接瀏覽,也可以按此下載IvsC.zip

IX.java

IX.java
package com.jsptw.example.interfac;
 
public interface IX {
  public void action();
}

CX.java

CX.java
package com.jsptw.example.jabstrac;
 
public abstract class CX {
  public abstract void action();
}

XObject.java

XObject.java
package com.jsptw.example;
 
import com.jsptw.example.interfac.IX;
import com.jsptw.example.jabstrac.CX;
 
public class XObject extends CX implements IX{
  public void action()
  {
  }
}

Measurement.java

Measurement.java
package com.jsptw.example;
 
public abstract class Measurement {
  public abstract long evaluate(XObject x, int freq);
}

ForInterface.java

ForInterface.java
package com.jsptw.example.interfac;
 
import com.jsptw.example.Measurement;
import com.jsptw.example.XObject;
 
public class ForInterface extends Measurement {
  public long evaluate(XObject x, int freq)
  {
    IX obj = x;
    long start = System.currentTimeMillis();
    for (int i = 0; i < freq; ++i)
      obj.action();
    long end = System.currentTimeMillis();
    
    return end - start;
  }
 
}

ForAbstractC.java

ForAbstractC.java
package com.jsptw.example.jabstrac;
 
import com.jsptw.example.Measurement;
import com.jsptw.example.XObject;
 
public class ForAbstractC extends Measurement {
  public long evaluate(XObject x, int freq)
  {
    CX obj = x;
    long start = System.currentTimeMillis();
    for (int i = 0; i < freq; ++i)
      obj.action();
    long end = System.currentTimeMillis();
    
    return end - start;
  }
 
}

MeasureProc.java

MeasureProc.java
package com.jsptw.example;
 
public class MeasureProc {
  public static final int UNIT = 1000000;
  
  public MeasureProc(XObject sample, Measurement m, int iterations)
  {
    this.sample = sample;
    this.iterations = iterations;
    measurement = m;
  }
  
  public MeasureProc(XObject sample, Measurement m)
  {
    this(sample, m, 1);
  }
  
  public MeasureProc(XObject sample)
  {
    this(sample, null);
  }
 
  public int getIterations()
  {
    return iterations;
  }
 
  public void setIterations(int i)
  {
    iterations = i;
  }
  
  public Measurement getMeasurement()
  {
    return measurement;
  }
 
  public void setMeasurement(Measurement measurement)
  {
    this.measurement = measurement;
  }
  
  public long getResult()
  {
    if (measurement == null)
      return 0;
    return measurement.evaluate(sample, iterations);
  }
  
  private Measurement measurement;
  private XObject sample;
  private int iterations;
}

附帶一提,我已經儘量做到公平,讓兩者的 package+class name 一樣長(這和 VM 搜尋 callee 有關)。另外我在計算時間時把 loop counter 的操作所好時間一起加進去(因為沒辦法把每次執行 action method 的時間分次計算然後求和),當然也包括了執行 System.currentTimeMillis() 本身所需要的時間(取得截止時間時)。

以下是結果:(powered by JFreeChart 0.9.8)

彈性與效率~Duncan

"彈性與效率本來不就是互斥?" 這是自然的道理,但你可以思考一下其他的程式語言(C+)如何在這二者身上取得妥協。C+ 使用 virtual function 達到 polymorphism,換來了彈性但付出的代價只有一點點,只是多一個間接取值的動作。

我有提到我個人是比較注重彈性。

文中提出的比較結果並不是區區幾百 ms 的問題,如果以這樣的觀點來看:測試的時間不單單只是 invokeinterface/invokevirtual 單一 instruction 的執行時間,還包括 loop counter 的操作在內,兩個 case 被測量的 instructions 相同(但數量不只一個)而相異的 instruction 只有一個(也就是我要比較的),這樣的結果並不是 invokeinterface 效率慢 invokevirtual 兩倍而已,因為如果我們試著把兩者相同的 instructions 所耗用的時間減掉,剩下單獨 invokeinterface/virtual 所消耗的時間,可以發現兩者的效能差異可能在 10 被以上,你看到的是一個極短的時間差異(幾百個 ms),我看到的是兩者的倍數關係。

再從另一個角度來看,如果 Java 支援 multi-inheritance,interface 還有存在的必要嗎?基本上可以用 pure abstract class 來代替 interface(C++ 就是以 pure abstract class 來表示 interface 的概念),而且不會有太大的 overhead,也不會提高多重繼承(多重介面)的設計難度。事實是主導者選擇了 interface 而捨棄 multi-inheritance 的支援,引進了支援 interface 的 instruction - invokeinterface,但是其效率卻(可能)差了 pure abstract class 所使用的 invokevirtual 十倍。
-
sun 主要是希望 Java 簡潔易用才做出這樣的決定,而不是因為效率的考量。

Site powered by a free Open Source Project / Non-profit License (more) of Confluence - the Enterprise wiki.
Learn more or evaluate Confluence for your organisation.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.1.5a Build:#411 Mar 16, 2006) - Bug/feature request - Contact Administrators