<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          教妹學(xué) Java 第 25 講:抽象類

          共 6756字,需瀏覽 14分鐘

           ·

          2021-04-28 19:13

          “二哥,你這明顯加快了更新的頻率呀!”三妹對(duì)于我最近的肝勁由衷的佩服了起來。

          “哈哈,是呀,這次不能再斷更了,我要再更 175 篇,總計(jì) 200 篇,給廣大的學(xué)弟學(xué)妹們一個(gè)完整的 Java 學(xué)習(xí)體系?!蔽覍?duì)未來充滿了信心。

          “那就開始吧?!比谜f。


          定義抽象類的時(shí)候需要用到關(guān)鍵字 abstract,放在 class 關(guān)鍵字前,就像下面這樣。

          abstract class AbstractPlayer {
          }

          關(guān)于抽象類的命名,《阿里的 Java 開發(fā)手冊(cè)》上有強(qiáng)調(diào),“抽象類命名要使用 Abstract 或 Base 開頭”,這條規(guī)約還是值得遵守的。

          抽象類是不能實(shí)例化的,嘗試通過 new 關(guān)鍵字實(shí)例化的話,編譯器會(huì)報(bào)錯(cuò),提示“類是抽象的,不能實(shí)例化”。

          雖然抽象類不能實(shí)例化,但可以有子類。子類通過 extends 關(guān)鍵字來繼承抽象類。就像下面這樣。

          public class BasketballPlayer extends AbstractPlayer {
          }

          如果一個(gè)類定義了一個(gè)或多個(gè)抽象方法,那么這個(gè)類必須是抽象類。

          當(dāng)我們嘗試在一個(gè)普通類中定義抽象方法的時(shí)候,編譯器會(huì)有兩處錯(cuò)誤提示。第一處在類級(jí)別上,提示“這個(gè)類必須通過 abstract 關(guān)鍵字定義”,見下圖。

          第二處在嘗試定義 abstract 的方法上,提示“抽象方法所在的類不是抽象的”,見下圖。

          抽象類中既可以定義抽象方法,也可以定義普通方法,就像下面這樣:

          public abstract class AbstractPlayer {
              abstract void play();
              
              public void sleep() {
                  System.out.println("運(yùn)動(dòng)員也要休息而不是挑戰(zhàn)極限");
              }
          }

          抽象類派生的子類必須實(shí)現(xiàn)父類中定義的抽象方法。比如說,抽象類 AbstractPlayer 中定義了 play() 方法,子類 BasketballPlayer 中就必須實(shí)現(xiàn)。

          public class BasketballPlayer extends AbstractPlayer {
              @Override
              void play() {
                  System.out.println("我是張伯倫,籃球場(chǎng)上得過 100 分");
              }
          }

          如果沒有實(shí)現(xiàn)的話,編譯器會(huì)提示“子類必須實(shí)現(xiàn)抽象方法”,見下圖。

          “二哥,抽象方法我明白了,那什么時(shí)候使用抽象方法呢?能給我講講它的應(yīng)用場(chǎng)景嗎?”三妹及時(shí)的插話道。

          “這問題問的恰到好處呀!”我扶了扶眼鏡繼續(xù)說。

          第一種場(chǎng)景。

          當(dāng)我們希望一些通用的功能被多個(gè)子類復(fù)用的時(shí)候,就可以使用抽象類。比如說,AbstractPlayer 抽象類中有一個(gè)普通的方法 sleep(),表明所有運(yùn)動(dòng)員都需要休息,那么這個(gè)方法就可以被子類復(fù)用。

          abstract class AbstractPlayer {
              public void sleep() {
                  System.out.println("運(yùn)動(dòng)員也要休息而不是挑戰(zhàn)極限");
              }
          }

          子類 BasketballPlayer 繼承了 AbstractPlayer 類:

          class BasketballPlayer extends AbstractPlayer {
          }

          也就擁有了 sleep() 方法。BasketballPlayer 的對(duì)象可以直接調(diào)用父類的 sleep() 方法:

          BasketballPlayer basketballPlayer = new BasketballPlayer();
          basketballPlayer.sleep();

          子類 FootballPlayer 繼承了 AbstractPlayer 類:

          class FootballPlayer extends AbstractPlayer {
          }

          也擁有了 sleep() 方法,F(xiàn)ootballPlayer 的對(duì)象也可以直接調(diào)用父類的 sleep() 方法:

          FootballPlayer footballPlayer = new FootballPlayer();
          footballPlayer.sleep();

          這樣是不是就實(shí)現(xiàn)了代碼的復(fù)用呢?

          第二種場(chǎng)景

          當(dāng)我們需要在抽象類中定義好 API,然后在子類中擴(kuò)展實(shí)現(xiàn)的時(shí)候就可以使用抽象類。比如說,AbstractPlayer  抽象類中定義了一個(gè)抽象方法 play(),表明所有運(yùn)動(dòng)員都可以從事某項(xiàng)運(yùn)動(dòng),但需要對(duì)應(yīng)子類去擴(kuò)展實(shí)現(xiàn),表明籃球運(yùn)動(dòng)員打籃球,足球運(yùn)動(dòng)員踢足球。

          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)上得過 100 分,");
              }
          }

          FootballPlayer 繼承了 AbstractPlayer 類,擴(kuò)展實(shí)現(xiàn)了自己的 play() 方法。

          public class FootballPlayer extends AbstractPlayer {
              @Override
              void play() {
                  System.out.println("我是C羅,我能接住任意高度的頭球");
              }
          }

          為了進(jìn)一步展示抽象類的特性,我們?cè)賮砜匆粋€(gè)具體的示例。假設(shè)現(xiàn)在有一個(gè)文件,里面的內(nèi)容非常簡(jiǎn)單,只有一個(gè)“Hello World”,現(xiàn)在需要有一個(gè)讀取器將內(nèi)容從文件中讀取出來,最好能按照大寫的方式,或者小寫的方式來讀。

          這時(shí)候,最好定義一個(gè)抽象類 BaseFileReader:

          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í)被子類訪問到。

          • readFile() 方法用來讀取文件,方法體里面調(diào)用了抽象方法 mapFileLine()——需要子類來擴(kuò)展實(shí)現(xiàn)大小寫的不同讀取方式。

          在我看來,BaseFileReader 類設(shè)計(jì)的就非常合理,并且易于擴(kuò)展,子類只需要專注于具體的大小寫實(shí)現(xiàn)方式就可以了。

          小寫的方式:

          class LowercaseFileReader extends BaseFileReader {
              protected LowercaseFileReader(Path filePath) {
                  super(filePath);
              }

              @Override
              protected String mapFileLine(String line) {
                  return line.toLowerCase();
              }
          }

          大寫的方式:

          class UppercaseFileReader extends BaseFileReader {
              protected UppercaseFileReader(Path filePath) {
                  super(filePath);
              }

              @Override
              protected String mapFileLine(String line) {
                  return line.toUpperCase();
              }
          }

          從文件里面一行一行讀取內(nèi)容的代碼被子類復(fù)用了。與此同時(shí),子類只需要專注于自己該做的工作,LowercaseFileReader 以小寫的方式讀取文件內(nèi)容,UppercaseFileReader 以大寫的方式讀取文件內(nèi)容。

          來看一下測(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,里面的內(nèi)容就是“Hello World”。文件的具體位置如下圖所示,我用的集成開發(fā)環(huán)境是 Intellij IDEA。

          在 resource 目錄下的文件可以通過 ClassLoader.getResource() 的方式獲取到 URI 路徑,然后就可以取到文本內(nèi)容了。

          輸出結(jié)果如下所示:

          [hello world]
          [HELLO WORLD]

          “完了嗎?二哥”三妹似乎還沉浸在聆聽教誨的快樂中。

          “是滴,這次我們系統(tǒng)化的學(xué)習(xí)了抽象類,可以說面面俱到了。三妹你可以把代碼敲一遍,加強(qiáng)了一些印象,電腦交給你了?!闭f完,我就跑到陽臺(tái)去抽煙了。

          “呼。。。。?!币粋€(gè)大大的眼圈飄散開來,又是愉快的一天~

          瀏覽 48
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  色婷婷亚洲AV无码精品 | 日本黄色片在线观看 | 亚洲无吗在线观看 | 熊猫成人 | 美女被干舒服偷拍自拍 |