八、Java 構造方法
假設現在有一個 Writer 類,它有兩個字段,姓名和年紀:
public class Writer {
private String name;
private int age;
@Override
public String toString() {
return "Writer{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
重寫了 toString() 方法,用于列印 Writer 類的詳情。由于沒有構造方法,意味着當我們建立 Writer 對象時,它的字段值并沒有初始化:
Writer writer = new Writer();
System.out.println(writer.toString());
輸出結果如下所示:
Writer{name='null', age=0}
name 是字元串類型,是以預設值為 null,age 為 int 類型,是以預設值為 0。
讓我們為 Writer 類主動加一個無參的構造方法:
public Writer() {
this.name = "";
this.age = 0;
}
構造方法也是一個方法,隻不過它沒有傳回值,預設傳回建立對象的類型。需要注意的是,目前構造方法沒有參數,它被稱為無參構造方法。如果我們沒有主動建立無參構造方法的話,編譯器會隐式地自動添加一個無參的構造方法。這就是為什麼,一開始雖然沒有構造方法,卻可以使用 new Writer() 建立對象的原因,隻不過,所有的字段都被初始化成了預設值。
接下來,讓我們添加一個有參的構造方法:
public Writer(String name, int age) {
this.name = name;
this.age = age;
現在,我們建立 Writer 對象的時候就可以通過對字段值初始化值了。
Writer writer1 = new Writer("沉默王二",18);
System.out.println(writer1.toString());
來看一下列印結果:
Writer{name='沉默王二', age=18}
可以根據字段的數量添加不同參數數量的構造方法,比如說,我們可以單獨為 name 字段添加一個構造方法:
public Writer(String name) {
為了能夠兼顧 age 字段,我們可以通過 this 關鍵字調用其他的構造方法:
this(name,18);
把作者的年齡都預設初始化為 18。如果需要使用父類的構造方法,還可以使用 super 關鍵字,手冊後面有詳細的介紹。
九、Java 抽象類
當我們要完成的任務是确定的,但具體的方式需要随後開個會投票的話,Java 的抽象類就派上用場了。這句話怎麼了解呢?搬個小闆凳坐好,聽我來給你講講。
01、抽象類的 5 個關鍵點
1)定義抽象類的時候需要用到關鍵字 abstract,放在 class 關鍵字前。
public abstract class AbstractPlayer {
關于抽象類的命名,阿裡出品的 Java 開發手冊上有強調,“抽象類命名要使用 Abstract 或 Base 開頭”,記住了哦。
2)抽象類不能被執行個體化,但可以有子類。
嘗試通過 new 關鍵字執行個體化的話,編譯器會報錯,提示“類是抽象的,不能執行個體化”。
通過 extends 關鍵字可以繼承抽象類,繼承後,BasketballPlayer 類就是 AbstractPlayer 的子類。
public class BasketballPlayer extends AbstractPlayer {
3)如果一個類定義了一個或多個抽象方法,那麼這個類必須是抽象類。
當在一個普通類(沒有使用 abstract 關鍵字修飾)中定義了抽象方法,編譯器就會有兩處錯誤提示。
第一處在類級别上,提醒你“這個類必須通過 abstract 關鍵字定義”,or 的那個資訊沒必要,見下圖。
第二處在方法級别上,提醒你“抽象方法所在的類不是抽象的”,見下圖。
4)抽象類可以同時聲明抽象方法和具體方法,也可以什麼方法都沒有,但沒必要。就像下面這樣:
abstract void play();
public void sleep() {
System.out.println("運動員也要休息而不是挑戰極限");
}
5)抽象類派生的子類必須實作父類中定義的抽象方法。比如說,抽象類中定義了 play() 方法,子類中就必須實作。
@Override
void play() {
System.out.println("我是張伯倫,籃球場上得過 100 分");
如果沒有實作的話,編譯器會提醒你“子類必須實作抽象方法”,見下圖。
02、什麼時候用抽象類
與抽象類息息相關的還有一個概念,就是接口,我們留到下一篇文章中詳細說,因為要說的知識點還是蠻多的。你現在隻需要有這樣一個概念就好,接口是對行為的抽象,抽象類是對整個類(包含成員變量和行為)進行抽象。
(是不是有點明白又有點不明白,别着急,翹首以盼地等下一篇文章出爐吧)
除了接口之外,還有一個概念就是具體的類,就是不通過 abstract 修飾的普通類,見下面這段代碼中的定義。
public class BasketballPlayer {
public void play() {
System.out.println("我是詹姆斯,現役第一人");
有接口,有具體類,那什麼時候該使用抽象類呢?
1)我們希望一些通用的功能被多個子類複用。比如說,AbstractPlayer 抽象類中有一個普通的方法 sleep(),表明所有運動員都需要休息,那麼這個方法就可以被子類複用。
雖然 AbstractPlayer 類可以不是抽象類——把 abstract 修飾符去掉也能滿足這種場景。但 AbstractPlayer 類可能還會有一個或者多個抽象方法。
BasketballPlayer 繼承了 AbstractPlayer 類,也就擁有了 sleep() 方法。
1
2
BasketballPlayer 對象可以直接調用 sleep() 方法:
BasketballPlayer basketballPlayer = new BasketballPlayer();
basketballPlayer.sleep();
FootballPlayer 繼承了 AbstractPlayer 類,也就擁有了 sleep() 方法。
public class FootballPlayer extends AbstractPlayer {
FootballPlayer 對象也可以直接調用 sleep() 方法:
FootballPlayer footballPlayer = new FootballPlayer();
footballPlayer.sleep();
2)我們需要在抽象類中定義好 API,然後在子類中擴充實作。比如說,AbstractPlayer 抽象類中有一個抽象方法 play(),定義所有運動員都可以從事某項運動,但需要對應子類去擴充實作。
BasketballPlayer 繼承了 AbstractPlayer 類,擴充實作了自己的 play() 方法。
System.out.println("我是張伯倫,我籃球場上得過 100 分,");
FootballPlayer 繼承了 AbstractPlayer 類,擴充實作了自己的 play() 方法。
System.out.println("我是C羅,我能接住任意高度的頭球");
3)如果父類與子類之間的關系符合 is-a 的層次關系,就可以使用抽象類,比如說籃球運動員是運動員,足球運動員是運動員。