抽象abstract
隨著繼承不斷疊代,子類越來越具體,而父類越來越通用。
類的設計必須保證子類與父類共有特徵,有時候我們將父類設計得非常抽象,以至於它沒有具體的實例,這樣的類稱為抽象類。
定義
abstract
屬於Java中的關鍵字,可以用來修飾類與方法,以下分別說明
abstract修飾類
- 此類不能實例化
- 必定有構造器被繼承
- 開發中都會提供抽象類的子類,讓子類形成實例來調用
- 不能修飾
final
類,final
類規定不能被繼承,玩毛線
abstract修飾方法
- 稱為抽象方法,只有聲明,沒有方法體 (就沒要讓你具體用)
- 此方法不能被調用
- 包含抽象方法的類,必定是一個抽象類。反之抽象類不一定要有抽象方法
- 實際開發中的調用必須是被子類繼承後重寫,所有的抽象方法都被重寫後此子類才能實例化(否則,存在繼承來的抽象方法你就是個抽象類)
- 不能修飾私有
private
方法,因為抽象就是為了被繼承,抽象與其矛盾 - 不能修飾靜態
static
方法,靜態方法跟類共存亡,可以直接被類調用,通常是去弄靜態屬性的,抽象與其矛盾 - 不能修飾
final
方法,final
方法規定是不能被重寫,抽象與其矛盾
應用-模板方法設計
在軟體開發中,實現某種功能時,整體中很固定、通用的方法,在父類中就寫好了;而其他不確定、易變的就先抽象起來,交給子類去實現
匿名子類的匿名對象
在一次性使用的場合,不想實際造一個匿名類的實體子類,可以在
new 匿名類()
後面接{}
,{
內直接重寫方法,範例:
abstract public class Person {
abstract public void work();
}
public class Student extends Person {
@Override
public void work() {
System.out.println("學生讀書");
}
public static void main(String[] args) {
method(new Student());
method(new Person() {
@Override
public void work() {
System.out.println("用一次的街友");
}
});
}
public static void method(Person person) {
person.work();
}
}
接口interface
繼承是is a的關係,例如學生是人,關係重點在於"是不是";而接口則是"能不能"的關係。Java中,接口跟類是並列的兩個結構(平級關係)
定義
-
使用關鍵字
interface 接口名{}
-
JDK7以前:只能定義全局常量
public static final
,和抽象方法public abstract
-
JDK8包含以上兩種,還可以定義靜態方法、預設方法
-
範例:
interface Flyable { public static final int MAX_SPEED = 7900; int MIN_SPEED = 1; // 關鍵字可以省略 public abstract void fly(); void stop(); // 關鍵字可以省略 }
-
接口中不能定義構造器,它是不能實例化的
-
接口通過讓類實現(
implement
)來使用 -
如果實現類覆蓋(重寫)了接口中的所有方法,則此實現類可以實例化;
-
反之,沒有完全實現接口的類,就只能是一個抽象類
-
舉例:
class Plane implements Flyable{ @Override public void fly() { System.out.println("飛機起飛"); } @Override public void stop() { System.out.println("飛機停止"); } }
-
一個類可以實現多個接口,(算是JAVA彌補單繼承的一個解決方案),格式範例:
class A2 extends A1 implement CC,DD,EE
-
接口之間可以繼承,而且可以多繼承,格式範例:
interface AA extends BB,CC
使用
- 接口實現了多態性
- 接口其實是定義了一種規範
- 比如我把某方法的形參用接口,管你啥類只要實現了這個接口,此類實例化的物件就可以調用這個方法,(用起來跟go的沒啥區別)
- Java8特性:
- 接口中定義的靜態方法,只能透過接口去調用(類似工具類)
- 預設方法:使用
default
修飾,實現類的物件,可以調用接口中的預設方法;若實現時重寫了方法,那調用的還是重寫的方法。只是說用default
修飾,可以免去把抽象方法一一實現的過程而直接調用預設的方法 - 如果實現類繼承的父類和實現的接口聲明了同名同參的方法,沒有重寫的情況下,優先調用父類的那個,稱為類優先原則
- 如果實現類實現了多個接口,其中存在複數同名同參的方法,在沒有重寫的情況下,報錯-接口衝突
- 假如有重寫,但想調用接口中的預設方法:
接口名.super.方法名
內部類inner class
Java允許在類A中聲明一個類B,此時B是內部類,A稱為外部類。他們在編譯時都會生成字節碼文件(XX.class)
內部類可分成:成員內部類(靜態、非靜態)與局部內部類(方法內、代碼塊內、構造器內),以下細說
成員內部類
-
作為外部類的成員
- 可以調用外部類的結構
- 可以被
static
修飾 - 可以被4種權限修飾
-
另一方面,本身作為一個類
- 類內可以定義屬性、方法、構造器…等等
- 內部類可以被繼承(甚至可以被其他類以"
extends 外部類.內部類
“的方式繼承,但如果內部類沒有static
的話,還需要提前建立一個封閉實例,很難搞),可以被abstract
修飾 - 可以被
final
修飾來表示不能被繼承
-
舉例:
public class Animal { String name; void eat() { System.out.println("eat something"); } static class Cat { void show() { System.out.println("小貓睡覺"); } } class Dog { void show() { System.out.println("小狗看門"); Animal.this.eat(); // 調用外部類的方法, // 其實"Animal.this."可以省略 } } }
-
實例化:
// 創建靜態的成員內部類 Animal.Cat c1 = new Animal.Cat(); c1.show(); // 創建非靜態的成員內部類實例,需要先有一個外部成員實例,再用這個實例去new Animal a1 = new Animal(); Animal.Dog d1 = a1.new Dog(); d1.show();
-
區分調用的屬性或方法:
用`this.`指向當前內部類,用"外部類名.this."指向外部類 當然最好還是不要有重名的屬性或方法
-
編譯時生成字節碼文件格式為”
外部類$內部類.class
"
局部內部類
-
局部內部類可以聲明在方法內、代碼塊內、構造器內
-
局部內部類中,若用到其外一層的屬性,則這個屬性必須是
final
的,舉例:public void someMethod() { final int age = 18; class AA { // 局部內部類 public void show(){ age=10; // 會報錯,因為這個age相當於是外面傳進來的副本 System.out.println(age); } } }
-
實際開發中較少用,比較可能見到是在android開發中,某方法為了實現某個接口,而暫時創建的一個局部內部類。類似上面提到過的匿名子類的匿名對象的用法,舉例:
// 比如點某按鈕就要跳出某訊息的方法 public void onCreate() { final int number = 10; // View.onClickListener()是一個接口需要被實現 button.setOnClickListener(new View.onClickListener() { public void onClick(){ System.out.println("點按鈕後產生的訊息"+number); } }); }
-
編譯時生成字節碼文件格式為"
外部類$數字 內部類.class
"
小結
- 抽象類:不能實例化、單繼承、有構造器
- 接口:不能實例化、多繼承、接口不會有構造器、可以有預設方法
上次修改於 2021-11-30