自從給小白寫了兩篇科普性質(zhì)的文章后优妙,我就有點(diǎn)一發(fā)不可收拾乘综,覺(jué)得很有必要繼續(xù)寫下去。因?yàn)橛凶x者留言“鼓勵(lì)”我說(shuō)套硼,“二哥卡辰,你真的是為小白操碎了心啊邪意!”我容易嗎九妈?我。
當(dāng)我們要完成的任務(wù)是確定的雾鬼,但具體的方式需要隨后開(kāi)個(gè)會(huì)投票的話萌朱,Java 的抽象類就派上用場(chǎng)了。這句話怎么理解呢策菜?搬個(gè)小板凳坐好晶疼,聽(tīng)我來(lái)給你講講。
01又憨、抽象類的 5 個(gè)關(guān)鍵點(diǎn)
1)定義抽象類的時(shí)候需要用到關(guān)鍵字 abstract
翠霍,放在 class
關(guān)鍵字前。
public abstract class AbstractPlayer {
}
關(guān)于抽象類的命名蠢莺,阿里出品的 Java 開(kāi)發(fā)手冊(cè)上有強(qiáng)調(diào)寒匙,“抽象類命名要使用 Abstract 或 Base 開(kāi)頭”,記住了哦躏将。
2)抽象類不能被實(shí)例化锄弱,但可以有子類考蕾。
嘗試通過(guò) new
關(guān)鍵字實(shí)例化的話,編譯器會(huì)報(bào)錯(cuò)棵癣,提示“類是抽象的辕翰,不能實(shí)例化”夺衍。
通過(guò) extends
關(guān)鍵字可以繼承抽象類狈谊,繼承后,BasketballPlayer 類就是 AbstractPlayer 的子類沟沙。
public class BasketballPlayer extends AbstractPlayer {
}
3)如果一個(gè)類定義了一個(gè)或多個(gè)抽象方法河劝,那么這個(gè)類必須是抽象類。
當(dāng)在一個(gè)普通類(沒(méi)有使用 abstract
關(guān)鍵字修飾)中定義了抽象方法矛紫,編譯器就會(huì)有兩處錯(cuò)誤提示赎瞎。
第一處在類級(jí)別上,提醒你“這個(gè)類必須通過(guò) abstract
關(guān)鍵字定義”颊咬,or 的那個(gè)信息沒(méi)必要务甥,見(jiàn)下圖。
第二處在方法級(jí)別上喳篇,提醒你“抽象方法所在的類不是抽象的”敞临,見(jiàn)下圖。
4)抽象類可以同時(shí)聲明抽象方法和具體方法麸澜,也可以什么方法都沒(méi)有挺尿,但沒(méi)必要。就像下面這樣:
public abstract class AbstractPlayer {
abstract void play();
public void sleep() {
System.out.println("運(yùn)動(dòng)員也要休息而不是挑戰(zhàn)極限");
}
}
5)抽象類派生的子類必須實(shí)現(xiàn)父類中定義的抽象方法炊邦。比如說(shuō)编矾,抽象類中定義了 play()
方法,子類中就必須實(shí)現(xiàn)馁害。
public class BasketballPlayer extends AbstractPlayer {
@Override
void play() {
System.out.println("我是張伯倫窄俏,籃球場(chǎng)上得過(guò) 100 分");
}
}
如果沒(méi)有實(shí)現(xiàn)的話,編譯器會(huì)提醒你“子類必須實(shí)現(xiàn)抽象方法”碘菜,見(jiàn)下圖凹蜈。
02、什么時(shí)候用抽象類
與抽象類息息相關(guān)的還有一個(gè)概念炉媒,就是接口踪区,我們留到下一篇文章中詳細(xì)說(shuō),因?yàn)橐f(shuō)的知識(shí)點(diǎn)還是蠻多的吊骤。你現(xiàn)在只需要有這樣一個(gè)概念就好缎岗,接口是對(duì)行為的抽象,抽象類是對(duì)整個(gè)類(包含成員變量和行為)進(jìn)行抽象白粉。
(是不是有點(diǎn)明白又有點(diǎn)不明白传泊,別著急鼠渺,翹首以盼地等下一篇文章出爐吧)
除了接口之外,還有一個(gè)概念就是具體的類眷细,就是不通過(guò) abstract
修飾的普通類拦盹,見(jiàn)下面這段代碼中的定義。
public class BasketballPlayer {
public void play() {
System.out.println("我是詹姆斯溪椎,現(xiàn)役第一人");
}
}
有接口普舆,有具體類,那什么時(shí)候該使用抽象類呢校读?
1)我們希望一些通用的功能被多個(gè)子類復(fù)用沼侣。比如說(shuō),AbstractPlayer 抽象類中有一個(gè)普通的方法 sleep()
歉秫,表明所有運(yùn)動(dòng)員都需要休息蛾洛,那么這個(gè)方法就可以被子類復(fù)用。
public abstract class AbstractPlayer {
public void sleep() {
System.out.println("運(yùn)動(dòng)員也要休息而不是挑戰(zhàn)極限");
}
}
雖然 AbstractPlayer 類可以不是抽象類——把 abstract
修飾符去掉也能滿足這種場(chǎng)景雁芙。但 AbstractPlayer 類可能還會(huì)有一個(gè)或者多個(gè)抽象方法轧膘。
BasketballPlayer 繼承了 AbstractPlayer 類,也就擁有了 sleep()
方法兔甘。
public class BasketballPlayer extends AbstractPlayer {
}
BasketballPlayer 對(duì)象可以直接調(diào)用 sleep()
方法:
BasketballPlayer basketballPlayer = new BasketballPlayer();
basketballPlayer.sleep();
FootballPlayer 繼承了 AbstractPlayer 類谎碍,也就擁有了 sleep()
方法。
public class FootballPlayer extends AbstractPlayer {
}
FootballPlayer 對(duì)象也可以直接調(diào)用 sleep()
方法:
FootballPlayer footballPlayer = new FootballPlayer();
footballPlayer.sleep();
2)我們需要在抽象類中定義好 API裂明,然后在子類中擴(kuò)展實(shí)現(xiàn)椿浓。比如說(shuō),AbstractPlayer 抽象類中有一個(gè)抽象方法 play()
闽晦,定義所有運(yùn)動(dòng)員都可以從事某項(xiàng)運(yùn)動(dòng)扳碍,但需要對(duì)應(yīng)子類去擴(kuò)展實(shí)現(xiàn)。
public abstract class AbstractPlayer {
abstract void play();
}
BasketballPlayer 繼承了 AbstractPlayer 類仙蛉,擴(kuò)展實(shí)現(xiàn)了自己的 play()
方法笋敞。
public class BasketballPlayer extends AbstractPlayer {
@Override
void play() {
System.out.println("我是張伯倫,我籃球場(chǎng)上得過(guò) 100 分荠瘪,");
}
}
FootballPlayer 繼承了 AbstractPlayer 類夯巷,擴(kuò)展實(shí)現(xiàn)了自己的 play()
方法。
public class FootballPlayer extends AbstractPlayer {
@Override
void play() {
System.out.println("我是C羅哀墓,我能接住任意高度的頭球");
}
}
3)如果父類與子類之間的關(guān)系符合 is-a
的層次關(guān)系趁餐,就可以使用抽象類,比如說(shuō)籃球運(yùn)動(dòng)員是運(yùn)動(dòng)員篮绰,足球運(yùn)動(dòng)員是運(yùn)動(dòng)員后雷。
03、具體示例
為了進(jìn)一步展示抽象類的特性,我們?cè)賮?lái)看一個(gè)具體的示例臀突。假設(shè)現(xiàn)在有一個(gè)文件勉抓,里面的內(nèi)容非常簡(jiǎn)單——“Hello World”,現(xiàn)在需要有一個(gè)讀取器將內(nèi)容讀取出來(lái)候学,最好能按照大寫的方式藕筋,或者小寫的方式。
這時(shí)候梳码,最好定義一個(gè)抽象類隐圾,比如說(shuō) BaseFileReader:
public abstract class BaseFileReader {
protected Path filePath;
protected BaseFileReader(Path filePath) {
this.filePath = filePath;
}
public List<String> readFile() throws IOException {
return Files.lines(filePath)
.map(this::mapFileLine).collect(Collectors.toList());
}
protected abstract String mapFileLine(String line);
}
filePath 為文件路徑,使用 protected 修飾边翁,表明該成員變量可以在需要時(shí)被子類訪問(wèn)翎承。
readFile()
方法用來(lái)讀取文件,方法體里面調(diào)用了抽象方法 mapFileLine()
——需要子類擴(kuò)展實(shí)現(xiàn)大小寫的方式符匾。
你看,BaseFileReader 設(shè)計(jì)的就非常合理瘩例,并且易于擴(kuò)展啊胶,子類只需要專注于具體的大小寫實(shí)現(xiàn)方式就可以了。
小寫的方式:
public class LowercaseFileReader extends BaseFileReader {
protected LowercaseFileReader(Path filePath) {
super(filePath);
}
@Override
protected String mapFileLine(String line) {
return line.toLowerCase();
}
}
大寫的方式:
public class UppercaseFileReader extends BaseFileReader {
protected UppercaseFileReader(Path filePath) {
super(filePath);
}
@Override
protected String mapFileLine(String line) {
return line.toUpperCase();
}
}
你看垛贤,從文件里面一行一行讀取內(nèi)容的代碼被子類復(fù)用了——抽象類 BaseFileReader 類中定義的普通方法 readFile()
焰坪。與此同時(shí),子類只需要專注于自己該做的工作聘惦,LowercaseFileReader 以小寫的方式讀取文件內(nèi)容某饰,UppercaseFileReader 以大寫的方式讀取文件內(nèi)容。
接下來(lái)善绎,我們來(lái)新建一個(gè)測(cè)試類 FileReaderTest:
public class FileReaderTest {
public static void main(String[] args) throws URISyntaxException, IOException {
URL location = FileReaderTest.class.getClassLoader().getResource("helloworld.txt");
Path path = Paths.get(location.toURI());
BaseFileReader lowercaseFileReader = new LowercaseFileReader(path);
BaseFileReader uppercaseFileReader = new UppercaseFileReader(path);
System.out.println(lowercaseFileReader.readFile());
System.out.println(uppercaseFileReader.readFile());
}
}
項(xiàng)目的 resource 目錄下有一個(gè)文本文件黔漂,名字叫 helloworld.txt。
可以通過(guò) ClassLoader.getResource()
的方式獲取到該文件的 URI 路徑禀酱,然后就可以使用 LowercaseFileReader 和 UppercaseFileReader 兩種方式讀取到文本內(nèi)容了炬守。
輸出結(jié)果如下所示:
[hello world]
[HELLO WORLD]
好了,我親愛(ài)的讀者朋友剂跟,以上就是本文的全部?jī)?nèi)容了减途。是不是感覺(jué)認(rèn)知邊界又拓寬了?
我是沉默王二曹洽,一枚有趣的程序員鳍置。如果覺(jué)得文章對(duì)你有點(diǎn)幫助,請(qǐng)微信搜索「 沉默王二 」第一時(shí)間閱讀送淆,回復(fù)【666】更有我為你精心準(zhǔn)備的 500G 高清教學(xué)視頻(已分門別類)税产。
本文 GitHub 已經(jīng)收錄,有大廠面試完整考點(diǎn),歡迎 Star砖第。
原創(chuàng)不易撤卢,莫要白票,請(qǐng)你為本文點(diǎn)個(gè)贊吧梧兼,這將是我寫作更多優(yōu)質(zhì)文章的最強(qiáng)動(dòng)力放吩。