介面和繼承

介面和繼承

介面

在這裡你可以學到更多關於介面的知識——有什麼用,如何用。

繼承

本章節描述如何在其他類派生出新類。即子類如何從父類中繼承欄位和方法。在這裡你將會學到所有的類都派生子Object,已經如何子類如何覆蓋從父類繼承而來的方法。本章節也涉及到類似介面的抽象類。

介面

不同群組程式設計師同意某個條約(contract),即闡明他們的軟體是如何互動的,在很多情況下都是非常重要的。每個群組可以在不清楚其他群組實現的情況下編寫程式碼。通常,介面就是這樣的條款

Java中的介面

在Java程式語言中,介面類似於類,它是引用型別,僅可以包含常量、方法簽名、預設方法(method signatures)、靜態方法和巢狀型別。僅僅預設方法和靜態方法可以擁有方法體。介面不能例項化,它僅僅可以被實現或者繼承其他介面。定義介面和定義類類似:

public interface OperateCar {
// constant declarations, if any
// method signatures
// An enum with values RIGHT, LEFT
int turn(Direction direction,
double radius,
double startSpeed,
double endSpeed);
int changeLanes(Direction direction,
double startSpeed,
double endSpeed);
int signalTurn(Direction direction,
boolean signalOn);
int getRadarFront(double distanceToCar,
double speedOfCar);
int getRadarRear(double distanceToCar,
double speedOfCar);
......
// more method signatures
}

方法簽名沒有大括號({}),以分號結束。

為了使用介面,你編寫一個實現介面的類。當例項化該類後,需要為宣告在介面內的每一個方法提供方法體,如:

public class OperateBMW760i implements OperateCar {
// the OperateCar method signatures, with implementation --
// for example:
int signalTurn(Direction direction, boolean signalOn) {
// code to turn BMW's LEFT turn indicator lights on
// code to turn BMW's LEFT turn indicator lights off
// code to turn BMW's RIGHT turn indicator lights on
// code to turn BMW's RIGHT turn indicator lights off
}
// other members, as needed -- for example, helper classes not 
// visible to clients of the interface
}

定義一個介面

介面由以下幾部分構成:

  • 訪問許可權修飾符。public or no-modifield.
  • 關鍵字interface
  • 介面名稱,如果有多個介面,用逗號分隔開。
  • 介面身體(body).
public interface GroupedInterface extends Interface1, Interface2, Interface3 {
// constant declarations
// base of natural logarithms
double E = 2.718282;
// method signatures
void doSomething (int i, double x);
int doSomethingElse(String s);
}

public表示所有任意位置的類都可以使用該介面。如果略去,即預設訪問級別,同包內可被其他類訪問。

介面可以繼承多個介面。用逗號分隔開。

介面體

介面體可以包含抽象方法、預設方法、靜態方法。抽象方法以分號結束,沒有方法體。預設方法用default關鍵字定義。靜態方法用static關鍵字定義。所有的方法隱含著public,所以你可以省略public。

此外,介面可以包含常量定義,這些常量隱含著public,static,和final。因此你可以省略這些關鍵字。

實現介面

為了定義實現介面的類,你得用關鍵字interface關鍵字。你可以實現一個或者多個介面,用逗號分隔開。按照約定,先extends在implements。如:

public class  TestOne extends TestTwo implements TestThree{
}

一個簡單的例子

public interface Relatable {
/*
this (object calling isLargerThan)
and other must be instances of 
the same class returns 1, 0, -1 
if this is greater than, 
equal to, or less than other
*/
public int isLargerThan(Relatable other);
}
public class RectanglePlus 
implements Relatable {
public int width = 0;
public int height = 0;
public Point origin;
// four constructors
public RectanglePlus() {
origin = new Point(0, 0);
}
public RectanglePlus(Point p) {
origin = p;
}
public RectanglePlus(int w, int h) {
origin = new Point(0, 0);
width = w;
height = h;
}
public RectanglePlus(Point p, int w, int h) {
origin = p;
width = w;
height = h;
}
// a method for moving the rectangle
public void move(int x, int y) {
origin.x = x;
origin.y = y;
}
// a method for computing
// the area of the rectangle
public int getArea() {
return width * height;
}
// a method required to implement
// the Relatable interface
public int isLargerThan(Relatable other) {
RectanglePlus otherRect 
= (RectanglePlus)other;//造型,告訴編譯器該物件真正是什麼
if (this.getArea() < otherRect.getArea())
return -1;
else if (this.getArea() > otherRect.getArea())
return 1;
else
return 0;               
}
}

將介面作為一個型別

當你定義一個介面,意味著你正在定義一種新的資料型別。當你在任意地方使用介面名稱作為型別名稱,賦值給該型別的值應須是實現該介面的類例項,如:

//variableTemp必須是interfaceName的實現類(可例項化)
//或是interfaceName型別的
interfaceName variable = variableTemp;
//object1必須是Object的子類
public Object findLargest(Object object1, Object object2) {
Relatable obj1 = (Relatable)object1;
Relatable obj2 = (Relatable)object2;
if ((obj1).isLargerThan(obj2) > 0)
return object1;
else 
return object2;
}

不斷髮展的介面

假設你編寫了一個介面叫DoIt:

public interface DoIt {
void doSomething(int i, double x);
int doSomethingElse(String s);
}

過了不久,你想增加一個方法,現在介面變成:

public interface DoIt {
void doSomething(int i, double x);
int doSomethingElse(String s);
boolean didItWork(int i, double x, String s);
}

如果你做出這樣的改變,那麼所有的實現類將不可用,因為它們不在實現舊的介面。使用該介面的程式設計師將會抗議。

應該在一開始就預測介面所有的使用者,如果不得不增加一些額外的方法,你有很多種選擇,比如你可以建立一個新的DoItPlus介面來繼承DoIt

public interface DoItPlus extends DoIt {
boolean didItWork(int i, double x, String s);
}

現在你可以選擇繼續使用舊介面還是升級介面了。

或者說,你可以把新加的方法宣告為預設方法,如下所示:

public interface DoIt {
void doSomething(int i, double x);
int doSomethingElse(String s);
default boolean didItWork(int i, double x, String s) {
// Method body 
}
}

你要為預設方法提供實現。你也可以定義位靜態方法。

預設方法

如果經過一段時間又新增了一個方法,那麼所有實現該介面的類都要重新編寫它們的實現,如果不想的話,可以將新增的方法定義成靜態方法,意味著程式設計師將新增的靜態方法當成工具方法(可以不管),而不是介面核心的方法。

預設方法可使你在介面中新增新的方法,併相容舊版本的介面。

擴充套件包含預設方法的介面

當你繼承一個包含預設方法的介面時,你可以有如下三種選擇:

  1. 不理會這個預設方法,即無作為。也就是讓你的實現類自動繼承這個預設方法。
  2. 重新定義這個預設方法,使它成為抽象方法(沒有實現,沒有方法體)。
  3. 重新定義預設方法,重新實現它。
//無作為
public interface AnotherTimeClient extends TimeClient { }
//使它變成抽象方法
public interface AbstractZoneTimeClient extends TimeClient {
public ZonedDateTime getZonedDateTime(String zoneString);
}
//重新實現
public interface HandleInvalidTimeZoneClient extends TimeClient {
default public ZonedDateTime getZonedDateTime(String zoneString) {
try {
return ZonedDateTime.of(getLocalDateTime(),ZoneId.of(zoneString)); 
} catch (DateTimeException e) {
System.err.println("Invalid zone ID: "   zoneString  
"; using the default time zone instead.");
return ZonedDateTime.of(getLocalDateTime(),ZoneId.systemDefault());
}
}
}

靜態方法

除預設方法之外,你可以在介面內部定義靜態方法(static method),靜態方法有如下特點:

  1. 靜態方法與類關聯,而不是物件。它由包含該靜態方法類的所有物件共享。
  2. 幫助你,讓你在類庫中更好的組織工具方法。讓工具方法集中一個地方而不是分散開。

小結

  • 介面宣告可以包括方法簽名、預設方法、靜態方法和常量定義。就預設方法和靜態方法可以有方法體(即可以實現)。
  • 如果一個類實現了一個介面,那麼該類必須實現該介面中所有的方法。
  • 一種型別所能存在的地方,介面名稱都可以將它取代。

繼承

在之前的章節中,多次提到了繼承。在Java語言中,類可以從其它類中派生出來,因此繼承了其他類的欄位(fields)和方法(methods)。


定義:從其它類中派生而來的類被稱為子類(派生類、擴充套件類)。被派生的類稱之為父類(基類、超類)。

除了Object沒有超類外,每一個類都有且僅有一個直接的父類(單繼承)。如果米誒有顯示地宣告一個類的父類,那麼該類的父類隱式地繼承自Object類。

A1繼承自A2,A2繼承自A3,A3繼承自A4,…,An繼承自Am,Am繼承自Object,循著繼承鏈,可以把所有的類的祖先類找到,即Object


使用繼承的想法很簡單,但卻強有力:當你新建立類中所包含的欄位和方法在已有類中存在,你可以從這些已有類中派生而來,這樣你就可以重用以後類的方法和欄位,從而不需要親自去編寫和除錯了。

Java平臺類繼承層次結構

這裡寫圖片描述

從上圖可以發現,所有的類都是Object的子類(後裔)。越往上,類越普遍(抽象);越往下,類越具體。

Object類位於java.lang包,定義了所有類共有的行為。在java平臺中,很多類派生自Object類,其他類又派生自這些類,以此類推,它們構成了類的層次結構。

繼承的一個小例子

public class Bicycle {
// the Bicycle class has three fields
public int cadence;
public int gear;
public int speed;
// the Bicycle class has one constructor
public Bicycle(int startCadence, int startSpeed, int startGear) {
gear = startGear;
cadence = startCadence;
speed = startSpeed;
}
// the Bicycle class has four methods
public void setCadence(int newValue) {
cadence = newValue;
}
public void setGear(int newValue) {
gear = newValue;
}
public void applyBrake(int decrement) {
speed -= decrement;
}
public void speedUp(int increment) {
speed  = increment;
}
}
public class MountainBike extends Bicycle {
// the MountainBike subclass adds one field
public int seatHeight;
// the MountainBike subclass has one constructor
public MountainBike(int startHeight,
int startCadence,
int startSpeed,
int startGear) {
super(startCadence, startSpeed, startGear);
seatHeight = startHeight;
}   
// the MountainBike subclass adds one method
public void setHeight(int newValue) {
seatHeight = newValue;
}   
}

子類能做什麼

無論子類位於哪個包,子類從父類那裡繼承所有的publicprotected成員。假如子類和父類處於同一個包,那麼子類也會把父類的包私有成員給繼承過來。你可以使用繼承把父類的成員給繼承過來,也可以取代父類成員、追加新成員等:

  • 繼承的欄位可以直接使用,和使用本類一樣。
  • 宣告和父類一樣的欄位,效果是隱藏父類的欄位(不鼓勵)。
  • 你可以宣告不存在與父類的欄位
  • 繼承的方法可以直接使用。
  • 重寫方法,即宣告的方法簽名和父類的一樣。
  • 隱藏,即編寫的靜態方法簽名和父類的一樣。
  • 新增方法,即宣告的方法不存在與父類。
  • 宣告一個建構函式,用super關鍵字顯式呼叫父類建構函式,或者隱式呼叫。

父類中的私有成員

雖說子類只能繼承父類的共有和保護成員,但是,假如父類擁有可以訪問私有欄位的共有或保護成員方法的話,子類也是可以訪問到父類的私有欄位的。(曲線救國,間接訪問)

巢狀類可以訪問外圍類的所有成員(包括私有),因此,如果public,protected巢狀類如果被子類所繼承,那麼該子類可以間接訪問父類的私有成員。

物件造型(Casting Object)

//我們說myBike是MountainBike型別的
public MountainBike myBike = new MountainBike();
/*
MountainBike繼承自或實現Bicycle或Object,也就是說以下表達成立:
1. MountainBike是Object。
2. Bicycle是Object
但是以下表示式不成立:
1. Object是Bicycle。只是可能是,Object可以是Person或其他的。
2. Object是MountainBike。
*/
Object obj = new MountainBike();//成立,隱式造型
//不成立,編譯器不知道obj實際上是MountainBike
MountainBike myBike = obj;
/* 
成立,顯示造型,告訴(許諾給)編譯器,obj就是MountainBike
假如obj實際不是MountainBike,則會丟擲異常。
可以用 obj instanceof MountainBike來測試,如果是,則造型。
如以下可以防止造型異常:
if (obj instanceof MountainBike) {
MountainBike myBike = (MountainBike)obj;
}
*/
MountainBike myBike = (MountainBike)obj;

多重繼承的狀態、實現、和型別(Multiple Inheritance of State, Implementation, and Type)

類和介面之間一個顯著的差異是類有欄位而介面沒有。此外你可以對類進行例項化,但是介面不能。

物件將它的狀態(state)儲存在欄位(fields)內。不能進行多繼承的原因之一是因為多繼承狀態( multiple inheritance of state),即從多個類中繼承欄位的能力。例如假設你能定義一個繼承自多個類的新類。當你例項化該類時,該物件將會繼承所有父類的多有欄位。假如不同父類的方法或者建構函式例項化同一個欄位,那哪個方法先執行?因為介面不包含欄位,所以你可以不用考慮多重繼承狀態所帶來的問題。

多種繼承的實現是從多個類中繼承方法的能力。當名稱衝突或者歧義時多重繼承會產生問題。當程式語言的編譯器允許多重繼承,遇到父類擁有同名方法時,它們有時候不能決定是哪個成員被訪問並呼叫。此外,程式設計師可能不知不覺中就在父類引入了名稱衝突的方法。預設方法引入了多重繼承實現的一種形式。一個類可以繼承多個介面,這些介面可能擁有同名的預設方法。編譯器提供一些規則來判定到底是哪個介面的預設方法被呼叫。

Java程式語言支援多重繼承型別,即實現一個或多個介面的能力。物件擁有多種型別:類本身的型別,所實現介面的型別。意味著假如一個變數被宣告為介面型別,那麼該變數可以引用任意例項化的物件。

public class A implements B,C,D{
}
B a = new A();//成立
B b = new A();//成立

至於多繼承的實現,即類可以繼承多個包含預設方法、靜態方法的介面。在這種情況下,編譯器或者使用者必須指定到底呼叫的是哪一個方法。

//A 會產生編譯錯誤
public class A implements B, C {
public static void main(String[] args) {
new A().methodName();
}
}
interface B {
default void methodName() {
System.out.println("別裝逼");
}
}
interface C {
default void methodName() {
System.out.println("別裝逼");
}
}

上述例子會報錯誤,資訊為A inherits unrelated defaults for methodName() from type B and C

重寫、隱藏方法(Overriding and Hiding Methods)

例項方法(Instance Methods)

子類擁有父類一樣的例項方法(方法簽名一樣,返回值一樣),則稱該方法重寫(Override)了父類的方法。

重寫方法解決了當子類和父類行為相似(方法實現近似相同,但卻有差異)時,可以在必要的時候修正子類的行為。重寫方法與被重寫方法擁有相同的方法名稱、引數的個數、引數的型別以及返回型別。此外重寫方法返回型別可以是被重寫方法的子型別,該子型別被稱為協變返回型別(covariant return type)。

public class Main extends Person {
@Override
public Main getALock() { //Main是Object的子型別,所以不會報錯
return new Main();
}
}
class Person {
public Object getALock(){
return new Object();
}
}

當重寫一個方法時,在方法的上面加上註解@Override,告訴編譯器你要重寫父類的某個方法,編譯器會偵測父類中有無你要重寫的方法,假如沒有,則報錯。

靜態方法(Static Methods)

假如子類定義了和父類具有方法簽名一樣的靜態類,則子類的該靜態方法隱藏了父類中的該靜態方法。

隱藏靜態方法和重寫例項方法的隱喻:

  • 對於重寫,呼叫那個例項方法,取決於呼叫方實際是哪個類的例項。
  • 對於隱藏靜態方法,哪個靜態方法被呼叫取決於呼叫方是子類還是父類。

例子如下:

public class Animal {
public static void testClassMethod() {
System.out.println("The static method in Animal");
}
public void testInstanceMethod() {
System.out.println("The instance method in Animal");
}
}
public class Cat extends Animal {
public static void testClassMethod() {
System.out.println("The static method in Cat");
}
public void testInstanceMethod() {
System.out.println("The instance method in Cat");
}
public static void main(String[] args) {
Cat myCat = new Cat();
Animal myAnimal = myCat;
Animal.testClassMethod();
myAnimal.testInstanceMethod();
}
}
The output from this program is as follows:
The static method in Animal //呼叫方是Animal
The instance method in Cat //實際是Cat例項

介面方法(Interfaces Methods)

繼承自包含預設方法和抽象方法的介面和繼承包含例項方法是一樣的。當父類(普通類、介面)擁有簽名一樣的預設方法時,Java編譯器遵循了繼承的規則,以解決命名衝突,規則如下:

  1. 例項方法優先於預設方法。
public class Horse {
public String identifyMyself() {
return "I am a horse.";
}
}
public interface Flyer {
default public String identifyMyself() {
return "I am able to fly.";
}
}
public interface Mythical {
default public String identifyMyself() {
return "I am a mythical creature.";
}
}
//如果去掉extends Horse,則會編譯錯誤
public class Pegasus extends Horse implements Flyer, Mythical {
public static void main(String... args) {
Pegasus myApp = new Pegasus();
System.out.println(myApp.identifyMyself());
}
}
output
I am a horse
  1. 被重寫的方法將被忽略。當超型別共享同一個祖先時會發生。
public interface Animal {
default public String identifyMyself() {
return "I am an animal.";
}
}
public interface EggLayer extends Animal {//重寫了Animal的預設方法
default public String identifyMyself() {
return "I am able to lay eggs.";
}
}
public interface FireBreather extends Animal { }
public class Dragon implements EggLayer, FireBreather {
public static void main (String... args) {
Dragon myApp = new Dragon();
System.out.println(myApp.identifyMyself());
}
}
output
I am able to lay eggs

假如兩個或者多個獨立的類、介面定義的預設方法衝突,或者預設方法與抽象方法衝突(同一類、介面內),則會產生編譯錯誤。此時,你必須明確地重寫超型別的方法。

public interface OperateCar {
// ...
default public int startEngine(EncryptedKey key) {
// Implementation
}
}
public interface FlyCar {
// ...
default public int startEngine(EncryptedKey key) {
// Implementation
}
}
public class FlyingCar implements OperateCar, FlyCar {
// ...
public int startEngine(EncryptedKey key) {
FlyCar.super.startEngine(key);//注意super關鍵字
OperateCar.super.startEngine(key);//注意super關鍵字
}
}

super關鍵字可以將命名衝突的預設方法給區分開,否則將產生編譯錯誤。

public interface Mammal {
String identifyMyself();
}
public class Horse {
public String identifyMyself() {
return "I am a horse.";
}
}
public class Mustang extends Horse implements Mammal {
public static void main(String... args) {
Mustang myApp = new Mustang();
System.out.println(myApp.identifyMyself());
}
}
output
I am a horse

從類中繼承而來的例項方法可以重寫介面內的抽象類,如上程式碼所示。

介面內的靜態類不會被繼承。

修飾詞(Modifiers)

子類重寫的方法的訪問許可權修飾詞必須和父類被重寫的一樣,或者更寬鬆。如假如父類被重寫方法是protected,則子類只能為protected或者public,不能為private.

靜態方法不能被重寫成例項方法,反之亦然。

總結

下面的表格總結了當你在子類中定義一個與父類擁有一樣的方法簽名時會發生什麼。

Superclass Instance MethodSuperclass Static Method
Subclass Instance MethodOverrides
Subclass Static MethodGenerates a compile-time error

你可以過載(overload)父類的方法,它們擁有別與父類的引數列表,所以是新的方法。

多型(Polymorphism)

字典定義中,多型是指生物學原理上有機體或者物種擁有不同的形態或階段。該原理也可以被物件導向程式語言所應用,如Java語言。子類可以定義它們自己的特殊行為,並共享父類的一些行為,狀態。

例如Bicycle有以下行為:

public void printDescription(){
System.out.println("\nBike is "   "in gear "   this.gear
" with a cadence of "   this.cadence  
" and travelling at a speed of "   this.speed   ". ");
}

建立兩個子類:

public class MountainBike extends Bicycle {
private String suspension;
public MountainBike(
int startCadence,
int startSpeed,
int startGear,
String suspensionType){
super(startCadence,
startSpeed,
startGear);
this.setSuspension(suspensionType);
}
public String getSuspension(){
return this.suspension;
}
public void setSuspension(String suspensionType) {
this.suspension = suspensionType;
}
public void printDescription() {
super.printDescription();
System.out.println("The "   "MountainBike has a"  
getSuspension()   " suspension.");
}
} 
public class RoadBike extends Bicycle{
// In millimeters (mm)
private int tireWidth;
public RoadBike(int startCadence,
int startSpeed,
int startGear,
int newTireWidth){
super(startCadence,
startSpeed,
startGear);
this.setTireWidth(newTireWidth);
}
public int getTireWidth(){
return this.tireWidth;
}
public void setTireWidth(int newTireWidth){
this.tireWidth = newTireWidth;
}
public void printDescription(){
super.printDescription();
System.out.println("The RoadBike"   " has "   getTireWidth()  
" MM tires.");
}
}
//測試類
public class TestBikes {
public static void main(String[] args){
Bicycle bike01, bike02, bike03;
bike01 = new Bicycle(20, 10, 1);
bike02 = new MountainBike(20, 10, 5, "Dual");
bike03 = new RoadBike(40, 20, 8, 23);
bike01.printDescription();
bike02.printDescription();
bike03.printDescription();
}
}
output
Bike is in gear 1 with a cadence of 20 and travelling at a speed of 10. 
Bike is in gear 5 with a cadence of 20 and travelling at a speed of 10. 
The MountainBike has a Dual suspension.
Bike is in gear 8 with a cadence of 40 and travelling at a speed of 20. 
The RoadBike has 23 MM tires.

執行時,JVM會為每個變數(bike01,bike02,bike03)呼叫物件適當的方法。呼叫哪個方法並不取決於變數的型別(例子中都是Bicycle)。這種行為被稱為虛方法呼叫( virtual method invocation ),該例子演示了Java語言中一個重要的特性——多型。

隱藏欄位(hiding Fields)

當子類擁有和父類一樣的欄位名稱時,子類的欄位會隱藏父類的欄位(即使型別不同)。在子類內,不能用簡單名稱訪問父類的欄位,欄位的訪問必須加上super關鍵字。通常情況下,不鼓勵隱藏父類的欄位,因為這使程式碼難以閱讀。

使用關鍵字super

訪問父類成員(Accessing Superclass Members)

你可以使用關鍵字super來訪問父類的成員,如:

public class Superclass {
public void printMethod() {
System.out.println("Printed in Superclass.");
}
}
public class Subclass extends Superclass {
// overrides printMethod in Superclass
public void printMethod() {
super.printMethod();//注意super
System.out.println("Printed in Subclass");
}
public static void main(String[] args) {
Subclass s = new Subclass();
s.printMethod();    
}
}
output
Printed in Superclass.
Printed in Subclass

子類構造器

public MountainBike(int startHeight, 
int startCadence,
int startSpeed,
int startGear) {
super(startCadence, startSpeed, startGear);
seatHeight = startHeight;
}   

呼叫父類的構造器形式為super()或者super(引數列表)。前者是呼叫父類無參建構函式,後者是有參建構函式。

必須先呼叫父類的建構函式。

假如一個建構函式不明確地呼叫父類的建構函式,則java編譯器會自動呼叫父類的無參建構函式,如果父類沒有無參建構函式,則會產生編譯錯誤。

無論明確或者不明確地呼叫父類建構函式,構造器鏈( constructor chaining)會一直執行,直到Object的建構函式被呼叫位置。

作為超類的Object

java.lang包中的Object類,位於類層次結構的最頂層。所有的類或直接或間接的是Object類的後裔。每一個你使用的或編寫的類都從Object那裡繼承了例項方法。你可以不使用這些方法,但是使用了,你也許會重寫這些方法:

  • protected Object clone() throws CloneNotSupportedException。建立並返回該物件的一個拷貝。
  • public boolean equals(Object obj)。表明其它物件與該物件是否相同(equals)
  • protected void finalize() throws Throwable。當物件沒有其他變數引用了,垃圾收集器會呼叫它。
  • public final Class getClass()。返回物件執行時的類。
  • public int hashCode()。返回物件的hash code值。
  • public String toString()。返回物件的String表現。

下面的方法多用於不同執行緒之間的同步活動中:

  • public final void notify()
  • public final void notifyAll()
  • public final void wait()
  • public final void wait(long timeout)
  • public final void wait(long timeout, int nanos)

編寫final類或者方法

方法宣告為final,說明該方法不能被重寫。使用場景,當一個建構函式內呼叫一個方法,那麼該方法通常被宣告為final,如果是非final,則子類可能會重寫它,導致不良後果。

類被宣告為final,則該類不能有子類。

抽象方法和抽象類

用關鍵字abstract宣告的類稱為抽象類——它可以不包含抽象方法。抽象類不能被例項化,但是可以被繼承

沒有實現(沒有花括號)的方法宣告稱為抽象類,如:

public abstract class GraphicObject {
// declare fields
// declare nonabstract methods
abstract void draw();
}

假如一個抽象類被一個類所繼承,那麼該類必須提供抽象類內所有抽象方法的實現,除非子類也是抽象。

如果介面內的方法既不是預設方法,又不是靜態方法,那麼該方法是抽象方法。你可以用abstract修飾它,但是沒必要(冗餘).

抽象類與即介面的對比

抽象類類似於介面。共同點列舉如下;

  1. 都不能例項化。
  2. 包含的方法都可以為抽象的或有實現的方法(具體方法)。

不同點:

  1. 抽象類內,你可以定義非靜態非final的欄位。
  2. 抽象類內,你可以定義public,protected,private具體的方法。
  3. 對抽象類而言,你只能繼承一個抽象類。
  4. 介面內,所有的欄位自動成為public,static,final的
  5. 介面內,定義的方法自動為public。
  6. 對介面而言,你可以繼承多個介面。

使用抽象類的場景:

  • 你想在相關的類之間共享程式碼。(繼承,可以共享,重用)
  • 你期望抽象類的子類擁有共同的方法或欄位,或者需要public意外的訪問許可權修飾符。
  • 你期望宣告非靜態,非final的欄位。這樣就可以訪問這些欄位,並修改它們。

使用介面的場景:

  • 你期望不相關的類實現你的介面。如介面Comparable或者Cloneable.
  • 你想規範特殊資料型別的行為,但不關心具體的實現。
  • 你想充分利用多繼承的特性。

例如AbstractMap擁有get, put, isEmpty, containsKey, and containsValue方法,被它的子類HashMap, TreeMapConcurrentHashMap所共享。

HashMap繼承了多個介面,如Serializable, Cloneable, and Map<K, V>.

抽象類舉例

這裡寫圖片描述

如一個畫圖應用程式,可以畫矩形,直線,貝塞爾曲線,原型。這些物件享有共同的狀態是位置、線條顏色等。

abstract class GraphicObject {
int x, y;
...
void moveTo(int newX, int newY) {
...
}
abstract void draw();
abstract void resize();
}
class Circle extends GraphicObject {
void draw() {
...
}
void resize() {
...
}
}
class Rectangle extends GraphicObject {
void draw() {
...
}
void resize() {
...
}
}

類成員

抽象類也可以宣告靜態方法或者靜態欄位。訪問他們可以用類引用,如 AbstractClass.staticMethod()

繼承的總結

  1. 除了Object物件外,所有類都有一個直接的父類。無論直接或者間接,類從父類內繼承欄位和方法。子類可以重寫父類方法,或者隱藏父類欄位。
  2. Object類是類層次結構中的頂端,所有的類都繼承它,它是所有類的根祖先。它擁有toString()、equals()、clone()、和getClass()等方法。
  3. 你可以用final來宣告類或者方法來防止被繼承或重寫。
  4. 抽象類可以被繼承,但不能例項化。抽象類可以包含抽象方法。