2013年9月10日 星期二

Interface



作者: 許裕永


在其他支援物件導向程式開發的程式語言(如C++)中,其類別的繼承機制,除了多層繼承之外,還有所有謂多重繼承」。就是每個類別可以有任意個上層類別。雖然說可以讓下層類別更具有擴充性,但是也增加了不必要的因擾。因為繼承多個上層類別,就代表了下層類別中的成員數量會很多,但並不是每一個都用得到,如何管理那一堆沒有用到的成員,就會非常困擾。
在Java中,每個類別只能繼承一個上層類別,雖然說沒有了多重繼承的困擾,但也限制了下層類別的擴充性。所以,Java使用了界面的機制,讓類別可以實 作一個或一個以上的界面,來支援類別擴充功能的需求。這種機制,等於讓Java有多重繼承的優點,卻也排除了多重繼承的缺點。
在實務上,我們可以把類別視為主,界面視為從。因為類別可以建構成物件,但界面不能建構成物件。界面是附屬於類別,讓些類別建構的物件可以擁有更多的功能。
假如我們要寫一支運動員的管理程式,規劃每個運動員為一個物件,物件中記錄著這個運動員的基本資料(資料成員),並且可以執行其運動專長的動作(方法成 員)。但是,每個運動員的專長並不一樣,部份運動員的專長有一種以上,那麼這些運動員物件的類別要怎麼設計呢?如果把所有專長的動作(方法)都寫在運動員 這個類別之中,表面上是可以,但是太過宄長,而且容易造成邏輯錯誤。因為宣告在類別中的方法成員,物件都可以呼叫。也就是這個類別所建構的運動員物件,也 可以呼叫到與他專長無關的方法。然而這個物件的資料成員所記錄的資料,只是符合這個運動員物件所擁有的專長的資料值,這些資料值在不是本運動員物件的專長 方法中運算時,自然會發生錯誤。(例如:有一個撞球運動員的身高資料為165,體能狀況為不適合劇烈運動。當這個物件執行打籃球的方法,結果如何?)
如果針對每一種專長設計一個類別,那建構運動員物件的類別只能繼承一種專長類別,又怎能讓運動員擁有一種以上的專長呢?
於是,我們可以把建構運動員的類別視為主,把專長視為從。把每個專長設計成界面,界面中宣告了這專長特有的資料成員及方法成員。運動員類別的下層類別,只 要實作了某個界面後,則該類別建構的物件,便可以執行運動員類別及界面中的方法成員。也就是說:我們把運動員類別當上層類別,然後開發一個新類別繼承運動 員類別,而且實作撞球專長界面,則這個新類別建構的物件,便是一個具有撞球專長的運動員物件。我們再開發一個新類別繼承運動員類別,並且實作撞球及慢跑界 面,則這個新類別建構的物件,便同時具有撞球及慢跑的專長。
再以視窗程式為例:我們在上一章所建立的視窗程式,是繼承了Frame這個類別,但是這個類別,預設並不能接受視窗、滑鼠及鍵盤的事件。為什麼?因為並不 是每個視窗都需要接受這三種事件,全寫進去增加物件的負擔。說話回來,也不只視窗需要接受這三種事件(例:按鈕需要接受滑鼠事件),總不能把接受這三種事 件的方法都撰寫在每一個視窗元件的類別之中吧(文字方塊接受視窗關閉的事件?)。
當然視窗程式的事件不只這三種。而Java把接受這些事件的方法分別設計成一個一個的事件監聽器界面。若我們開發的視窗程式需要接受滑鼠事件,便實作滑鼠監聽器界面,需要接受鍵盤事件便實作鍵盤監聽器界面…,以此類推。
1. interface
真要開發一個實用的界面,對大部份讀者而言,還是一件遙遠的事。我們現在要學的重點,是如何應用Java提供的界面。但至少我們必須先知道,界面是怎麼開發的,它裏面有些什麼東西。
1-1 宣告界面
語法:[存取限制][abstract] interface InterfaceName{}
存取限制:同類別,public或default(package)
abstract:預設宣告,此界面預設就是abstract。不加是抽象,加了也是抽象,加或不加都合法。
InterfaceName:命名原則和類別一樣。
例:
public abstract interface Baseball{}
public interface Baseball{}
interface Baseball{}
以上三個宣告都合法,三個界面都是abstract,但是注意第三個界面的存取限制為package。
例:
public final interface Baseball{}
此宣告不合法,界面不可以是final。
1-2 宣告資料成員
語法:[public][static][final]資料型別 memberName = value;
public、static及final:都是預設宣告。就是說:「不論宣告句中是否有這三個修飾詞,這個資料成員就一定是public、static及final」。
value:因為資料成員是final,所以宣告時必須立刻指派初值。
因為界面中不像類別有方法可撰寫指派敍述,所以final的成員一定要在宣告時立刻指派。
例:
int x=1;
public int x=1;
public static int x=1;
public static final int x=1;
static int x=1;
final int x=1;
static final int x=1;
以上宣告均合法,而且意義相同,請注意存取限制預設是public,不是package。
例:
int x;
不合法,必須在宣告時指派初值。
1-3 宣告方法成員
語法:[public][abstract]運算結果型別 memberName(參數列);
public及abstract:都是預設宣告。就是說:「不論宣告句中是否有這兩個修飾詞,這個方法成員就一定是public及abstract」。
;:因為是abstract,所以必需用‘;’結尾。
例:
void catchBall();
public void catchBall();
public abstract void catchBall();
abstract void catchBall();
abstract public void catchBall();
以上均為合法宣告,而且意義相同,請注意存取限制預設是public,不是package。
例:
void catchBall(){}
final void catchBall();
static void catchBall();
private void catchBall();
protected void catchBall();
以上宣告均不合法,第一個是用大括號定義的錯,其餘均為修飾詞和abstract或public衝突的錯。
1-4 界面的繼承
界面和類別有兩個很重要的差異:1、界面不可以建構成物件。2、界面可以多重繼承。也就是說,界面可以用extends來繼承一個以上的父界面。
但是請不要搞混了,類別繼承類別,界面繼承界面,是不可以亂繼承的哦!
 
2. implements
從上一小節中我們了解界面的成員只有兩種:1、類別常數成員。2、抽象方法成員。也難怪它不能建構成物件,因為它擁有抽象方法。所以它只能讓類別實作之後,然後該類別建構成的物件才能執行它宣告的方法。
2-1 實作界面
何謂實作界面:1、用implements宣告界面名稱(用‘,’區隔可以宣告一個以上之界面名稱)。2、overriding界面中宣告的所有抽象方法。
請注意:界面中的方法,全部都是抽象方法,一定要全部overriding。否則,不要說建構成物件,連類別都要宣告為抽象類別。
特別、特別、特別、特別、特別注意,再注意:類別中overriding界面的方法時,存取修飾詞必須(一定要)宣告為public,如果不宣告其預設限制為package,將違反overriding的規則,造成編譯錯誤。
因為界面中的方法沒有宣告存取限制預設為public,但類別中的方法沒有宣告存取限制預設為package。
 
範例一:簡易的界面宣告及實作
檔案一:MyInterface.java
public interface MyInterface{
 int a=8;
 void abc();
 void def();
}
請先將本檔案編譯為class檔。
檔案二:Sample.java
public class Sample implements MyInterface{
 public void abc(){}
 public void def(){
  System.out.println("YUNG");
 }
 public static void main(String[] args){
  System.out.println(MyInterface.a);
  System.out.println(Sample.a);
  Sample s=new Sample();
  s.def();
 }
}
列印結果:8 8 YUNG
範例主題:1、界面中宣告的成員,即使不加修飾詞,也都有預設的修飾詞宣告。2、類別實作界面時,必須overriding界面中宣告的所有方法。3、單 單把{}加在方法名稱右側,也是overriding,意思是定義為什麼都不做。4、宣告在界面中的資料成員預設是static,可以用類別名稱取得,也 可以用界面名稱取得,但是不要修改,因為是final。
 
範例二:視窗程式新增監聽器
檔案名稱:MyWindow.java
import java.awt.*;
import java.awt.event.*;
public class MyWindow extends Frame{
 public MyWindow(){
  super("大笨視窗");
  setBounds(200,200,180,100);
  MyListener ml=new MyListener();
  addWindowListener(ml);
 }
 public static void main(String[] args){
  new MyWindow().show();
 }
}
class MyListener implements WindowListener{
 public void windowActivated(WindowEvent e) {}
 public void windowClosed(WindowEvent e) {}
 public void windowClosing(WindowEvent e) {
  System.exit(0);
 }
 public void windowDeactivated(WindowEvent e) {}
 public void windowDeiconified(WindowEvent e) {}
 public void windowIconified(WindowEvent e) {}
 public void windowOpened(WindowEvent e) {}
}
範例主題:1、開發新類別實作界面。2、視窗物件安裝視窗監聽器。
關於視窗元件與事件監聽器的關係,我們要先了解:使用者觸發的事件是如何傳遞到程式之中的。各位,當你對著視窗上的按鈕,按下了滑鼠左鍵,是誰接到這個訊 息,是視窗?不是。是作業系統。硬體設備的資訊不就是由作業系統來管理的嗎!作業系統接收個訊息後,傳遞給JVM(Java虛擬機器),JVM再把這個訊 息建構成相對應的Event物件(例:WindowEvent,MouseEvent,KeyEvent),JVM再把這個Event物件傳遞給執行中的 視窗元件。重點是,這個視窗元件是否有安裝該Event的Listener(監聽器),如果有,則執行監聽器中撰寫的程式碼,如果沒有,則這個Event 物件便會自動消毀。
以本範例而言,在MyWindow的建構方式中,有一行新增視窗監聽器的程式敍述句:addWindowListener(),此句便是要把小括號中的視 窗監聽器物件安裝為本視窗的視窗監聽器。安裝了此監聽器之後,只要JVM傳遞WindowEvent物件進來,便由監聽器來接收並執行。
 
範例三:視窗類別實作監聽器
檔案:MyWindow.java
import java.awt.*;
import java.awt.event.*;
public class MyWindow extends Frame implements WindowListener{
 public MyWindow(){
 super("大笨視窗");
 setBounds(200,200,180,100);
  addWindowListener(this);
 }
 public void windowActivated(WindowEvent e) {}
 public void windowClosed(WindowEvent e) {}
 public void windowClosing(WindowEvent e) {
  dispose();
 }
 public void windowDeactivated(WindowEvent e) {}
 public void windowDeiconified(WindowEvent e) {}
 public void windowIconified(WindowEvent e) {}
 public void windowOpened(WindowEvent e) {}
 public static void main(String[] args){
  new MyWindow().show();
 }
}
範例主題:1、下層類別繼承上層類別取得主要功能,再實作界面取得附屬功能。2、MyWindow建構的物件,是一個視窗(Frame)物件,也是一個視窗監聽器(WindowListener)物件(多型,後續章節介紹)。
本範例中,視窗監聽器不再另外寫一個類別,而是讓視窗類別也實作視窗監聽器界面,那麼本類別建構的物件,不但是一個視窗物件,也是一個視窗監聽器物件(多 型,將再後續章節討論)。也就是說:本類別建構的物件,可以當作視窗物件,也可以當作視窗監聽器物件。addWindowListener()中的參數 this,指的便是執行addWindowListener這個方法的物件,那又是誰在執行這個方法,就是本物件啊(昏倒)。就是這個類別的物件在執行 addWindowListener時,用自己當參數的意思。只是,執行addWindowListener時是用視窗物件的身份來執行,當參數時用的是 視窗監聽器物件的身份來當參數。(如果還是看不懂,請先略過,看完多型後再回來看)
兩個範例並陳,主要是要讓讀者了解,addWindowListener()中的參數,要的是一個視窗監聽器物件,而不是一定要本類別實作之後再用 this當參數。當然範例三的寫法,比範例二好,至少不是分成兩個類別,但還有其他更簡略的寫法,請參閱上一章的MyWindow。(匿名類別,後續章節 介紹)
 

3. 認證重點
3-1 interface
  • 财 界面的成員只有兩種:1、類別常數成員。2、抽象方法成員。
  • 财 界面預設就是abstract。不加是抽象,加了也是抽象,加或不加都合法。
  • 财 界面不可以是final。
  • 财 不論宣告句中是否有修飾詞,interface中的資料成員就一定是public、static及final。
  • 财 因為資料成員是final,所以宣告時必須立刻指派初值(物件)。
  • 财 論宣告句中是否有修飾詞,interface中方法成員就一定是public及abstract。
  • 财 界面可以多重繼承。也就是說,界面可以用extends來繼承一個以上的父界面。
  • 财 類別繼承類別,界面繼承界面,是不可以亂繼承的哦!
3-2 implements
  • 财 實作界面:1、用implements宣告界面名稱(用‘,’區隔可以宣告一個以上之界面名稱)。2、overriding界面中宣告的所有抽象方法。
  • 财 界面中的方法,全部都是抽象方法,一定要全部overriding。否則,不要說建構成物件,連類別都要宣告為抽象類別。
  • 财 類別中overriding界面的方法時,存取修飾詞必須(一定要)宣告為public,如果不宣告其預設限制為package,將違反overriding的規則,造成編譯錯誤。

沒有留言:

張貼留言