--- title: Java抽象类,看这一篇就够了,豁然开朗 shortTitle: Java抽象类 description: Java抽象类是面向对象编程的一种核心概念,它用于声明具有共同特征的类的基本结构。抽象类可以包含抽象方法和具体方法,以实现代码重用和逻辑抽象。本文详细介绍了Java抽象类的定义、使用场景、继承规则和实例,助您更好地理解和应用抽象类。 category: - Java 核心 tag: - 面向对象编程 head: - - meta - name: keywords content: Java,Java抽象类,抽象类,抽象方法, 继承 --- # 5.10 Java抽象类 “二哥,你这明显加快了更新的频率呀!”三妹对于我最近的肝劲由衷的佩服了起来。 “哈哈,是呀,我要给广大的学弟学妹们一个完整的 Java 学习体系,记住我们的口号,**学 Java 就上二哥的 Java 进阶之路**。”我对未来充满了信心。 “那就开始吧。”三妹说。 ### 01、定义抽象类 定义抽象类的时候需要用到关键字 `abstract`,放在 `class` 关键字前,就像下面这样。 ```java abstract class AbstractPlayer { } ``` 关于抽象类的命名,《[阿里的 Java 开发手册](https://javabetter.cn/pdf/ali-java-shouce.html)》上有强调,“抽象类命名要使用 Abstract 或 Base 开头”,这条规约还是值得遵守的,真正做到名如其意。 ### 02、抽象类的特征 抽象类是不能实例化的,尝试通过 `new` 关键字实例化的话,编译器会报错,提示“类是抽象的,不能实例化”。 ![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/object-class/abstract-01.png) 虽然抽象类不能实例化,但可以有子类。子类通过 `extends` 关键字来继承抽象类。就像下面这样。 ```java public class BasketballPlayer extends AbstractPlayer { } ``` 如果一个类定义了一个或多个抽象方法,那么这个类必须是抽象类。 当我们尝试在一个普通类中定义抽象方法的时候,编译器会有两处错误提示。第一处在类级别上,提示“这个类必须通过 `abstract` 关键字定义”,见下图。 ![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/object-class/abstract-02.png) 第二处在尝试定义 abstract 的方法上,提示“抽象方法所在的类不是抽象的”,见下图。 ![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/object-class/abstract-03.png) 抽象类中既可以定义抽象方法,也可以定义普通方法,就像下面这样: ```java public abstract class AbstractPlayer { abstract void play(); public void sleep() { System.out.println("运动员也要休息而不是挑战极限"); } } ``` 抽象类派生的子类必须实现父类中定义的抽象方法。比如说,抽象类 AbstractPlayer 中定义了 `play()` 方法,子类 BasketballPlayer 中就必须实现。 ```java public class BasketballPlayer extends AbstractPlayer { @Override void play() { System.out.println("我是张伯伦,篮球场上得过 100 分"); } } ``` 如果没有实现的话,编译器会提示“子类必须实现抽象方法”,见下图。 ![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/object-class/abstract-04.png) ### 03、抽象类的应用场景 “二哥,抽象方法我明白了,那什么时候使用抽象方法呢?能给我讲讲它的应用场景吗?”三妹及时的插话道。 “这问题问的恰到好处呀!”我扶了扶眼镜继续说。 #### **01)第一种场景** 当我们希望一些通用的功能被多个子类复用的时候,就可以使用抽象类。比如说,AbstractPlayer 抽象类中有一个普通的方法 `sleep()`,表明所有运动员都需要休息,那么这个方法就可以被子类复用。 ```java abstract class AbstractPlayer { public void sleep() { System.out.println("运动员也要休息而不是挑战极限"); } } ``` 子类 BasketballPlayer 继承了 AbstractPlayer 类: ```java class BasketballPlayer extends AbstractPlayer { } ``` 也就拥有了 `sleep()` 方法。BasketballPlayer 的对象可以直接调用父类的 `sleep()` 方法: ```java BasketballPlayer basketballPlayer = new BasketballPlayer(); basketballPlayer.sleep(); ``` 子类 FootballPlayer 继承了 AbstractPlayer 类: ```java class FootballPlayer extends AbstractPlayer { } ``` 也拥有了 `sleep()` 方法,FootballPlayer 的对象也可以直接调用父类的 `sleep()` 方法: ```java FootballPlayer footballPlayer = new FootballPlayer(); footballPlayer.sleep(); ``` 这样是不是就实现了代码的复用呢? #### **02)第二种场景** 当我们需要在抽象类中定义好 API,然后在子类中扩展实现的时候就可以使用抽象类。比如说,AbstractPlayer 抽象类中定义了一个抽象方法 `play()`,表明所有运动员都可以从事某项运动,但需要对应子类去扩展实现,表明篮球运动员打篮球,足球运动员踢足球。 ```java abstract class AbstractPlayer { abstract void play(); } ``` BasketballPlayer 继承了 AbstractPlayer 类,扩展实现了自己的 `play()` 方法。 ```java public class BasketballPlayer extends AbstractPlayer { @Override void play() { System.out.println("我是张伯伦,我篮球场上得过 100 分,"); } } ``` FootballPlayer 继承了 AbstractPlayer 类,扩展实现了自己的 `play()` 方法。 ```java public class FootballPlayer extends AbstractPlayer { @Override void play() { System.out.println("我是C罗,我能接住任意高度的头球"); } } ``` 为了进一步展示抽象类的特性,我们再来看一个具体的示例。 >PS:[网站](https://javabetter.cn/oo/abstract.html)评论区说涉及到了文件的读写以及 Java 8 的新特性,不适合新人,如果觉得自己实在是看不懂,跳过,等学了 IO 流再来看也行。如果说是为了复习 Java 基础知识,就不存在这个问题了。 假设现在有一个文件,里面的内容非常简单,只有一个“Hello World”,现在需要有一个读取器将内容从文件中读取出来,最好能按照大写的方式,或者小写的方式来读。 这时候,最好定义一个抽象类 BaseFileReader: ```java /** * 抽象类,定义了一个读取文件的基础框架,其中 mapFileLine 是一个抽象方法,具体实现需要由子类来完成 */ abstract class BaseFileReader { protected Path filePath; // 定义一个 protected 的 Path 对象,表示读取的文件路径 /** * 构造方法,传入读取的文件路径 * @param filePath 读取的文件路径 */ protected BaseFileReader(Path filePath) { this.filePath = filePath; } /** * 读取文件的方法,返回一个字符串列表 * @return 字符串列表,表示文件的内容 * @throws IOException 如果文件读取出错,抛出该异常 */ public List readFile() throws IOException { return Files.lines(filePath) // 使用 Files 类的 lines 方法,读取文件的每一行 .map(this::mapFileLine) // 对每一行应用 mapFileLine 方法,将其转化为指定的格式 .collect(Collectors.toList()); // 将处理后的每一行收集到一个字符串列表中,返回 } /** * 抽象方法,子类需要实现该方法,将文件中的每一行转化为指定的格式 * @param line 文件中的每一行 * @return 转化后的字符串 */ protected abstract String mapFileLine(String line); } ``` - filePath 为文件路径,使用 protected 修饰,表明该成员变量可以在需要时被子类访问到。 - `readFile()` 方法用来读取文件,方法体里面调用了抽象方法 `mapFileLine()`——需要子类来扩展实现大小写的不同读取方式。 在我看来,BaseFileReader 类设计的就非常合理,并且易于扩展,子类只需要专注于具体的大小写实现方式就可以了。 小写的方式: ```java class LowercaseFileReader extends BaseFileReader { protected LowercaseFileReader(Path filePath) { super(filePath); } @Override protected String mapFileLine(String line) { return line.toLowerCase(); } } ``` 大写的方式: ```java class UppercaseFileReader extends BaseFileReader { protected UppercaseFileReader(Path filePath) { super(filePath); } @Override protected String mapFileLine(String line) { return line.toUpperCase(); } } ``` 从文件里面一行一行读取内容的代码被子类复用了。与此同时,子类只需要专注于自己该做的工作,LowercaseFileReader 以小写的方式读取文件内容,UppercaseFileReader 以大写的方式读取文件内容。 来看一下测试类 FileReaderTest: ```java 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()); } } ``` 在项目的 resource 目录下建一个文本文件,名字叫 helloworld.txt,里面的内容就是“Hello World”。文件的具体位置如下图所示,我用的集成开发环境是 Intellij IDEA。 ![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/object-class/abstract-05.png) 在 resource 目录下的文件可以通过 `ClassLoader.getResource()` 的方式获取到 URI 路径,然后就可以取到文本内容了。 输出结果如下所示: ``` [hello world] [HELLO WORLD] ``` ### 04、抽象类总结 好了,对于抽象类我们简单总结一下: - 1、抽象类不能被实例化。 - 2、抽象类应该至少有一个抽象方法,否则它没有任何意义。 - 3、抽象类中的抽象方法没有方法体。 - 4、抽象类的子类必须给出父类中的抽象方法的具体实现,除非该子类也是抽象类。 “完了吗?二哥”三妹似乎还沉浸在聆听教诲的快乐中。 “是滴,这次我们系统化的学习了抽象类,可以说面面俱到了。三妹你可以把代码敲一遍,加强了一些印象,电脑交给你了。”说完,我就跑到阳台去抽烟了。 “呼。。。。。”一个大大的眼圈飘散开来,又是愉快的一天~ ---- GitHub 上标星 9000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 9000+ 的 Java 教程](https://javabetter.cn/overview/) 微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。 ![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/gongzhonghao.png)