有時候,我們可能想要構造一個很抽象的父類對象,它可能僅僅代表一個分類或抽象概念,它的執行個體沒有任何意義,是以不希望它能被執行個體化。例如:有一個父類“ 水果(fruit)”,它有幾個子類“蘋果(apple)”、“橘子(orange)”、“香蕉(banana)”等。水果在這裡僅僅隻是作為一個分類,顯然水果的執行個體沒有什麼意義(就好像一個人如果告訴你他買了一些水果但是卻不告訴你是蘋果還是橘子,你很難想象他到底買的是什麼。)。而水果類又要能被子類化,這就要求我們使用抽象類(abstract class)來解決這個問題。
在java中,通過在class關鍵字前增加abstract修飾符,就可以将一個類定義成抽象類。抽象類不能被執行個體化。例如:
定義抽象類水果(fruit)
public abstract class fruit {
……
}
如果我們試圖用以下語句來獲得一個執行個體,将無法編譯成功。
fruit fruit = new fruit();
而我們仍然可以構造水果類的子類,如:
子類“蘋果(apple)”
public class apple extends fruit {
……
}
子類“橘子(orange)”
public class orange extends fruit {
這樣就達到我們的目的了。
抽象類除了能象普通類一樣可以擁有一般的屬性和方法,也可以擁有抽象方法(abstract method)。例如:
抽象類“形狀(shape)”擁有抽象方法draw()。
public abstract class shape {
public abstract void draw();
抽象方法與抽象的行為相對應,通常是這個行為對父對象沒有意義,而子對象有具體動作。例如方法draw()對于類shape沒有意義,而類shape的子類矩形(rectangle)的方法draw()可以有實際的動作(根據矩形的四個頂點畫出矩形的四個邊),子類圓(circle)的方法draw()也可以有實際的動作(根據圓心和半徑畫出圓周)。
抽象類可以有抽象方法也可以沒有抽象方法;但是如果一個類有抽象方法,那這個類隻能定義為抽象類。
如果按照以下代碼類“形狀(shape)”仍然擁有抽象方法draw(),但沒有定義為抽象類,将會編譯失敗。
public class shape {
抽象方法還有一個特點是,它強迫子類要麼仍然保持抽象性(即不具體實作該方法并仍然定義為抽象類),要麼具體表現出這個方法的行為(實作具體的動作或者通過抛出unsupportedoperationexception異常來表明不支援該行為)。這樣也可以強化多态性。
上面簡要分析了抽象類,下面談談接口(interface)。java語言使用關鍵字interface定義一個接口。接口也是抽象對象,它甚至比抽象類更抽象。接口中的方法都是抽象方法。
一個接口可以繼承其他接口;一個類通過關鍵字implements聲明要實作一個接口,并具體實作接口的方法。
例如:有一個接口interfacea,
public interface interfacea {
void methoda();
}
類classa實作接口interfacea。
public class classa implements interfacea {
public void methoda() {
system.out.println("methoda of classa implements interfacea");
}
如果是抽象類實作一個接口,那麼抽象類中可以不具體實作接口的方法(保持其抽象性),而由其子類去實作。
抽象類classb實作接口interfacea,但是沒有具體實作方法methoda(),
public abstract class classb { }
子類classbsub實作接口interfacea,但是沒有具體實作方法methoda(),
public class classbsub {
public void methoda() {
system.out.println("methoda of classbsub the subclass of classb");
}
接口和抽象類顯著的共同點是接口和抽象類都可以有抽象方法。
接口和抽象類的不同點有:
(1)抽象類可以有執行個體變量,而接口不能擁有執行個體變量,接口中的變量都是靜态(static)的常量(final)。
(2)抽象類可以有非抽象方法,而接口隻能有抽象方法。
java中,類與類之間是不能多繼承的。java之是以禁止類與類之間的多繼承是因為多繼承有很大的缺點。
多繼承雖然能使子類同時擁有多個父類的特征,但是其缺點也是很顯著的,主要有兩方面:
(1)如果在一個子類繼承的多個父類中擁有相同名字的執行個體變量,子類在引用該變量時将産生歧義,無法判斷應該使用哪個父類的變量。例如:
類classa:
public class classa {
protected int varsame = 0;
類classb:
public class classb {
protected int varsame = 1;
子類classc:(假設允許類與類之間多繼承)
public class classc extends classa, classb {
public void printout() {
system.out.println(super.varsame);
public static void main(string[] args) {
classc classc = new classc();
classc.printout();
上面程式的運作結果會是什麼呢?輸出0還是1?
(2)如果在一個子類繼承的多個父類中擁有相同方法,子類中有沒有覆寫該方法,那麼調用該方法時将産生歧義,無法判斷應該調用哪個父類的方法。例如:
system.out.println(0);
system.out.println(1);
public static void main(string[] args) {
classa classa = new classc();
classa.printout(); // ------------------------- a行
classb classb = new classc();
classb.printout(); // ------------------------- b行
classc classc = new classc();
classc.printout(); //------------------------- c行
}
}
上面程式的運作結果會是什麼呢?a、b、c三行的輸出是0還是1?
正因為有以上的緻命缺點,是以java中禁止一個類繼承多個父類;但是幸運的是java提供了接口,并能通過接口的功能獲得多繼承的許多優點而又摒棄了類與類多繼承的缺點。
java允許一個接口繼承多個父接口,也允許一個類實作多個接口,而這樣的多繼承有上面提到的缺點馬?
答案是沒有,這是由接口的抽象性決定的。
正如前面介紹的,在接口中不能有執行個體變量,隻能有靜态的常量,不能有具體的方法(包含方法體),隻能有抽象方法,是以也就摒棄了多繼承的缺點。
對于一個類實作多個接口的情況,因為接口隻有抽象方法,具體方法隻能由實作接口的類實作,在調用的時候始終隻會調用實作類的方法(不存在歧義),是以不存在多繼承的第二個缺點;而又因為接口隻有靜态的常量,但是由于靜态變量是在編譯期決定調用關系的,即使存在一定的沖突也會在編譯時提示出錯;而引用靜态變量一般直接使用類名或接口名,進而避免産生歧義,是以也不存在多繼承的第一個缺點。
對于一個接口繼承多個父接口的情況也一樣不存在這些缺點。
請看以下示例。
接口a:
int len = 1;
void output();
接口b:
public interface interfaceb {
int len = 2;
void output();
接口sub繼承接口a和接口b:
public interface interfacesub extends interfacea, interfaceb { }
類xyz實作接口sub:
public class xyz implements interfacesub {
public void output() {
system.out.println("output in class xyz.");
public void outputlen(int type) {
switch(type) {
case interfacea.len:
system.out.println("len of interfacea=."+type);
break;
case interfaceb.len:
system.out.println("len of interfaceb=."+type);
}
public static void main(string[] args) {
xyz xyz= new xyz ();
xyz .output();
xyz .outputlen();
}
以上代碼不存在什麼問題,但是如果試圖編寫以下存在沖突的代碼,則會編譯失敗。
xyz xyz = new xyz();
int len = xyz.len;
system.out.println(len);
由于引入了接口,java顯得非常靈活,也使得java中的多态性更加富有魔力。