1. 抽象類
什麼是抽象類:
Java語言中,可以通過把類或者類中的某些方法聲明爲 abstract 來表示一個類是抽象類。抽象類跟普通類區別不大,唯一的區別就是抽象類中可以包含抽象方法,且抽象類不可以被實例化。
那我們爲什麼要使用抽象類呢?
這裏我個人覺得,在自下而上的類的繼承層次結構中上移,位於上層的類更具有通用性,甚至有可能,在高層中的一些基類,有一些方法是不用實現的,具有抽象性的。例如:
一個最高的基類是動物,裏面可能有各種方法(動作),例如:吃、行動、睡覺,對於動物這個最高的基類,這些方法都不需要實現,讓它的子類去實現就好,就相當於這些方法都是抽象的,意味着將實現交給了子類。而對於子類,例如有一個子類羊,它向下還有更多的子類,例如:a類羊、b類羊、等等,對於羊這個類,對於行動這個方法,不同的子類羊行動的方式不同,因此這裏依舊不需要實現,所以依舊這些方法可以定義爲抽象的。
因此,在上面這個例子中,可以看出抽象類的使用場景:一個類有部分or全部的方法是不需要實現的(交付給子類實現),這樣子的類爲抽象類。
擴展抽象類的方式
其實從上面的例子中,我們不難總結出擴展抽象類的兩種選擇:
- 在子類中保留部分的抽象方法,這樣子子類也要定義爲抽象類
- 在子類中實現全部的抽象方法,這樣子子類就可以不定義爲抽象類
幾個不能和 abstract 共存的關鍵字
- final:被 final 修飾的類不能有子類(不能被繼承)。而被 abstract 修飾的類一定是一個父類(一定要被繼承),因此矛盾,無法共存
- private:抽象類中的私有的抽象方法,不被子類所知,就無法被複寫。而抽象方法是一定要在子類中被實現的,因此矛盾,無法共存
- static:如果static可以修飾抽象方法,那麼連對象都省了,直接類名調用就可以了,可是抽象方法運行沒意義
關於抽象類的幾個注意點
- 即使類中沒有抽象方法,也可以將類定義爲抽象類
- 抽象類不可以被實例化
- 抽象方法和抽象類都必須被 abstract 關鍵字修飾
2. 接口
什麼是接口
接口是對類的一組需求描述,這些類要遵從接口描述的統一格式進行定義。
Java8 對接口的變動有什麼
主要變動的地方有兩個:
- 第一,在接口中增加了靜態方法,可以直接在接口處申明靜態方法並實現,調用方法同普通類的靜態方法,舉例:
// 在接口中寫上靜態方法
public interface MyInterface {
public static void test() {
System.out.println("實現靜態方法");
}
}
// 在其他類中調用
public class Main {
public static void main(String[] args) {
MyInterface.test();
}
}
調用結果:
- 第二,Java8 中允許接口中包含具有具體實現的方法,該方法稱爲“默認方法”,使用 default 關鍵字修飾
// 在接口中實現默認方法
public interface MyInterface {
default void testDefault() {
System.out.println("實現默認方法");
}
}
// 在實現類中調用方法
public class Main implements MyInterface{
public static void main(String[] args) {
Main m = new Main();
m.testDefault();
}
}
執行結果:
但在接口中可以寫實現類的話,就會引發兩個問題:
- 第一,如果類A實現兩個接口,兩個接口各自實現了一個相同的方法,我在類A中調用該方法,是調用哪個接口的默認方法呢?
- 第二,如果類A實現的一個接口中的默認方法,跟類A繼承的類中的一個方法相同,我調用的是基類的方法,還是接口的默認方法呢?
關於這兩個問題,有一個原則:默認方法的“類優先”原則
什麼意思呢?就是:若一個接口中定義了一個默認方法,而另外一個父類或接口又定義了一個同名的方法時:
- 選擇父類中的方法,如果一個父類中提供了具體的實現,那麼接口具有相同名稱和參數的默認方法會被忽略
- 接口衝突。如果實現的兩個接口都提供了一個具有相同名稱和參數列表的方法(不管方法是否是默認方法),那麼在實現類中必須覆蓋方法來解決衝突。
具體例子:
① 父類接口衝突
// 父類
public class Father {
public void sayHello() {
System.out.println("hello, this is Father");
}
}
// 接口
public interface MyInterface {
default void sayHello() {
System.out.println("hello, this is MyInterface");
}
}
// 子類
public class Main extends Father implements MyInterface{
public static void main(String[] args) {
Main m = new Main();
m.sayHello();
}
}
執行的結果:父類中的實現
② 兩個接口衝突
// 接口1:
public interface MyInterface {
default void sayHello() {
System.out.println("hello, this is MyInterface");
}
}
// 接口2:
public interface MyInterface1 {
default void sayHello() {
System.out.println("hello, this is MyInterface1");
}
}
當類A同時實現這兩個接口時,會發現提示:
因此,我們需要在類A中覆蓋該方法,才能使程序正常運行
3. 抽象類和接口的相同點
- 都不能被實例化
- 接口的實現類或抽象類的子類,都只有在實現了接口或抽象類中的方法後,才能被實例化
- 從 Java8 開始,抽象類和接口都可以有方法的實現(Java8 之前接口只有定義)
4. 抽象類和接口的不同點
- 接口需要實現(implements),且一個類可以實現多個接口,抽象類需要被繼承(extends),且只能單繼承,即一個類只有一個父類
- 接口的設計理念是 “has - a” ,關係,即強調特定功能的實現,而抽象類強調所屬關係,即 “is - a” 關係
- 接口中定義的成員變量默認爲 public static final,不能被修改且需要賦初始值。而抽象類的成員變量跟普通類的相同