Java 是個純粹的面向對象語言,沒有過程式的寫法,所有操作都是基於對象。面向對象的設計可以更好的解決系統維護、擴展、和代碼重用的需求,代碼過多後過程式的邏輯組織會變得異常複雜,所以就發展出了面向對象式編程。而面向對象的基本特性就是封裝、繼承、與多態。
一、繼承
extends 關鍵字表示繼承某個已存在的類,已存在的類稱為「超類」、「基類」、或「父類」,而新繼承的類稱為「子類」、「派生類」。子類可以方法父類中的所有非 private 域,也即子類通過繼承獲得了父類具有的特性和能力。
定義一個 Person 父類:
public class Person{private String name;private int age;public Person(String name, int age){this.name = name;this.age = age;}public String getName(){return name;}public int getAge(){return age;}}
定義一個 Student 子類:
public class Student extends Person{private String school;// 構造函數public Student(String name, int age, String school){super(name, age); // 調用父類構造函數this.school = school;}public String getSchool() {return school;}}
寫個 Main 類測試下:
public class Main{public static void main(String[] args){Student s = new Student("小明", 12, "xx小學");System.out.println(s.getName()); // 輸出:小明System.out.println(s.getAge()); // 輸出:12System.out.println(s.getSchool()); // 輸出:xx小學}}
從輸出可以看出,子類 Student 並沒有 getName、getAge 方法,但通過繼承獲得了父類的能力。在上一章我們知道 this、super 兩個關鍵字,this 訪問自身,super 訪問父類,這裡調用父類的構造函數使用 super。
繼承可以獲得父類的能力,但有時候獲得的能力並不太理想,這時可以在子類中重寫父類的方法,使其符合子類的需求。同樣 Student 類:
public class Student extends Person{private String school; // 構造函數public Student(String name, int age, String school){super(name, age); // 調用父類構造函數this.school = school;}public String getSchool(){return school;}// 重寫 getName 方法public String getName(){return "我是一名學生,我叫" + super.getName();}}
通過重寫父類中的方法,使子類具有不同的能力。
二、繼承層級
上面的例子只講了一層繼承關係,當然也可以進行多次繼承。A 繼承 B,B 繼承 C,C 繼承 D 理論上可以一直繼續,但是肯定不建議嵌套太多繼承關係,這會讓程序變得複雜且性能降低。
Java 可以有多個繼承層次,一個類也可以有多條繼承鏈路,M 繼承 A,N 繼承 A。但是 Java 不支持多繼承,即 M 同時繼承 A 又 繼承 B,Java 不允許多繼承降低了複雜度,如果要實現多繼承,可以通過接口 interface 形式實現。所以 Java 類之間只有單一繼承關係,沒有多繼承。問什麼這樣設計,要問語言設計者。
三、多態
我們通過類實現了封裝,又通過繼承獲得了父類的功能,減少了重複代碼,最後我們通過擴展子類,或者重寫子類中的方法實現了多態。就好比一個母親生了多個孩子,每個孩子性格差異各不相同。
子類生成的對象都屬於父類類型。如:
注意看,所有子類實例都可以賦值給父類變量,但僅能調用父類定義的方法。因為 JVM 會把 s2 當作 Person 類實例看待,父類中不存在的則不能訪問,又因為 s 和 s2 實際指向的是同一對象,所以調用 getName 方法還是子類的方法。
子類實例引用可以賦值給父類變量,但父類引用不能賦值給子類變量。因為這是一個包含關係,父類包含子類(人 Person 包含學生 Student),反之則不成立。
如何判斷一個對象是某個類生成的,或通過某個類構造的,可以用 instanceof 語句。instanceof 可以判斷某個對象是通過那個類(或其父類)構造的,如:
四、接口
接口不是類,它是對類的一組需求描述和行為規範。為什麼是需求描述,因為接口只是定義行為方法,並不實現方法,而繼承它的類規定必須實現接口定義的方法。類通過實現接口定義的方法而受接口的約束,所以說接口是類的需求描述和行為規範。接口和類是兩種不同的數據類型。
接口使用 interface 關鍵字定義,格式如下:
public interface InterfaceName{public type funcName(type args, ...); // type 是數據類型}
繼承類使用 extends,而繼承接口使用 implements。
定義一個 Calculate 接口:
public interface Calculate{// 定義一個加法運算public int add(int i, int j);}
聲明一個類 Calculator 繼承 Calculate:
public class Calculator implements Calculate{// 實現 add 方法public int add(int i, int j){return i+j;}}
從上文可知 Java 中類不能實現多繼承,但是這裡類可以通過接口實現多繼承,多個接口之間用 ',' 分割,繼承的類必須實現所有繼承接口中定義的方法。如:
public class Calculator implements Calculate1,Calculate2,Calculate3{... // 實現 Calculate1,Calculate2,Calculate3 中定義的所有方法}
五、接口與抽象類
上一章中我們還知道類可以被定義成抽象的,抽象類中可以定義屬性和方法,也可以定義抽象方法。
那麼什麼時候使用抽象類,什麼時候使用接口呢?為了準確使用我們需要需要了解下抽象類和接口的區別。
抽象類:將一組共同的行為組合在一起,作為一個抽象類。在抽象類中可以定義抽象方法,也可以實現方法。抽象類屬於類不能多繼承,可以用 instanceof 判斷從屬關係。
接口:對類的一組需求描述和行為規範。接口不能實現具體方法,但可以實現多繼承。
那什麼時候使用抽象類,什麼時候使用接口呢?
1.如果抽象出來的一組行為,和子類具有從屬關係(is-a),那麼就使用抽象類。如果沒有從屬關係,只是希望具備某個能力,那麼使用接口。
2.如果需求變更會導致父類的功能擴展,那麼使用抽象類修改的代碼會少些,而使用接口所有子類都需要調整。
如何使用抽象類或者接口,有時需要反覆的代碼練習。