abstract.md 11.2 KB
Newer Older
沉默王二's avatar
沉默王二 已提交
1
---
沉默王二's avatar
沉默王二 已提交
2 3
title: Java抽象类,看这一篇就够了,豁然开朗
shortTitle: Java抽象类
沉默王二's avatar
沉默王二 已提交
4
description: Java抽象类是面向对象编程的一种核心概念,它用于声明具有共同特征的类的基本结构。抽象类可以包含抽象方法和具体方法,以实现代码重用和逻辑抽象。本文详细介绍了Java抽象类的定义、使用场景、继承规则和实例,助您更好地理解和应用抽象类。
沉默王二's avatar
沉默王二 已提交
5
category:
沉默王二's avatar
沉默王二 已提交
6
  - Java 核心
沉默王二's avatar
沉默王二 已提交
7
tag:
沉默王二's avatar
沉默王二 已提交
8 9 10 11
  - 面向对象编程
head:
  - - meta
    - name: keywords
沉默王二's avatar
沉默王二 已提交
12
      content: Java,Java抽象类,抽象类,抽象方法, 继承
沉默王二's avatar
沉默王二 已提交
13
---
沉默王二's avatar
沉默王二 已提交
14

沉默王二's avatar
沉默王二 已提交
15
# 5.10 Java抽象类
沉默王二's avatar
jvm  
沉默王二 已提交
16

沉默王二's avatar
沉默王二 已提交
17 18
“二哥,你这明显加快了更新的频率呀!”三妹对于我最近的肝劲由衷的佩服了起来。

沉默王二's avatar
沉默王二 已提交
19
“哈哈,是呀,我要给广大的学弟学妹们一个完整的 Java 学习体系,记住我们的口号,**学 Java 就上二哥的 Java 进阶之路**。”我对未来充满了信心。
沉默王二's avatar
沉默王二 已提交
20 21 22

“那就开始吧。”三妹说。

沉默王二's avatar
沉默王二 已提交
23
### 01、定义抽象类
沉默王二's avatar
沉默王二 已提交
24 25 26 27 28 29 30 31

定义抽象类的时候需要用到关键字 `abstract`,放在 `class` 关键字前,就像下面这样。

```java
abstract class AbstractPlayer {
}
```

沉默王二's avatar
沉默王二 已提交
32
关于抽象类的命名,《[阿里的 Java 开发手册](https://javabetter.cn/pdf/ali-java-shouce.html)》上有强调,“抽象类命名要使用 Abstract 或 Base 开头”,这条规约还是值得遵守的,真正做到名如其意。
沉默王二's avatar
沉默王二 已提交
33

沉默王二's avatar
沉默王二 已提交
34
### 02、抽象类的特征
沉默王二's avatar
沉默王二 已提交
35

沉默王二's avatar
沉默王二 已提交
36 37
抽象类是不能实例化的,尝试通过 `new` 关键字实例化的话,编译器会报错,提示“类是抽象的,不能实例化”。

沉默王二's avatar
沉默王二 已提交
38
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/object-class/abstract-01.png)
沉默王二's avatar
沉默王二 已提交
39 40 41 42 43 44 45 46 47 48 49 50

虽然抽象类不能实例化,但可以有子类。子类通过 `extends` 关键字来继承抽象类。就像下面这样。

```java
public class BasketballPlayer extends AbstractPlayer {
}
```

如果一个类定义了一个或多个抽象方法,那么这个类必须是抽象类。

当我们尝试在一个普通类中定义抽象方法的时候,编译器会有两处错误提示。第一处在类级别上,提示“这个类必须通过 `abstract` 关键字定义”,见下图。

沉默王二's avatar
沉默王二 已提交
51
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/object-class/abstract-02.png)
沉默王二's avatar
沉默王二 已提交
52 53 54

第二处在尝试定义 abstract 的方法上,提示“抽象方法所在的类不是抽象的”,见下图。

沉默王二's avatar
沉默王二 已提交
55
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/object-class/abstract-03.png)
沉默王二's avatar
沉默王二 已提交
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81

抽象类中既可以定义抽象方法,也可以定义普通方法,就像下面这样:

```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 分");
    }
}
```

如果没有实现的话,编译器会提示“子类必须实现抽象方法”,见下图。

沉默王二's avatar
沉默王二 已提交
82
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/object-class/abstract-04.png)
沉默王二's avatar
沉默王二 已提交
83

沉默王二's avatar
沉默王二 已提交
84
### 03、抽象类的应用场景
沉默王二's avatar
沉默王二 已提交
85

沉默王二's avatar
沉默王二 已提交
86 87 88 89
“二哥,抽象方法我明白了,那什么时候使用抽象方法呢?能给我讲讲它的应用场景吗?”三妹及时的插话道。

“这问题问的恰到好处呀!”我扶了扶眼镜继续说。

沉默王二's avatar
沉默王二 已提交
90
#### **01)第一种场景**
沉默王二's avatar
沉默王二 已提交
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131

当我们希望一些通用的功能被多个子类复用的时候,就可以使用抽象类。比如说,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();
```

这样是不是就实现了代码的复用呢?

沉默王二's avatar
沉默王二 已提交
132
#### **02)第二种场景**
沉默王二's avatar
沉默王二 已提交
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163

当我们需要在抽象类中定义好 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罗,我能接住任意高度的头球");
    }
}
```

沉默王二's avatar
沉默王二 已提交
164 165
为了进一步展示抽象类的特性,我们再来看一个具体的示例。

沉默王二's avatar
沉默王二 已提交
166
>PS:[网站](https://javabetter.cn/oo/abstract.html)评论区说涉及到了文件的读写以及 Java 8 的新特性,不适合新人,如果觉得自己实在是看不懂,跳过,等学了 IO 流再来看也行。如果说是为了复习 Java 基础知识,就不存在这个问题了。
沉默王二's avatar
沉默王二 已提交
167 168

假设现在有一个文件,里面的内容非常简单,只有一个“Hello World”,现在需要有一个读取器将内容从文件中读取出来,最好能按照大写的方式,或者小写的方式来读。
沉默王二's avatar
沉默王二 已提交
169 170 171 172

这时候,最好定义一个抽象类 BaseFileReader:

```java
沉默王二's avatar
沉默王二 已提交
173 174 175
/**
 * 抽象类,定义了一个读取文件的基础框架,其中 mapFileLine 是一个抽象方法,具体实现需要由子类来完成
 */
沉默王二's avatar
沉默王二 已提交
176
abstract class BaseFileReader {
沉默王二's avatar
沉默王二 已提交
177
    protected Path filePath; // 定义一个 protected 的 Path 对象,表示读取的文件路径
沉默王二's avatar
沉默王二 已提交
178

沉默王二's avatar
沉默王二 已提交
179 180 181 182
    /**
     * 构造方法,传入读取的文件路径
     * @param filePath 读取的文件路径
     */
沉默王二's avatar
沉默王二 已提交
183 184 185 186
    protected BaseFileReader(Path filePath) {
        this.filePath = filePath;
    }

沉默王二's avatar
沉默王二 已提交
187 188 189 190 191
    /**
     * 读取文件的方法,返回一个字符串列表
     * @return 字符串列表,表示文件的内容
     * @throws IOException 如果文件读取出错,抛出该异常
     */
沉默王二's avatar
沉默王二 已提交
192
    public List<String> readFile() throws IOException {
沉默王二's avatar
沉默王二 已提交
193 194 195
        return Files.lines(filePath) // 使用 Files 类的 lines 方法,读取文件的每一行
                .map(this::mapFileLine) // 对每一行应用 mapFileLine 方法,将其转化为指定的格式
                .collect(Collectors.toList()); // 将处理后的每一行收集到一个字符串列表中,返回
沉默王二's avatar
沉默王二 已提交
196 197
    }

沉默王二's avatar
沉默王二 已提交
198 199 200 201 202
    /**
     * 抽象方法,子类需要实现该方法,将文件中的每一行转化为指定的格式
     * @param line 文件中的每一行
     * @return 转化后的字符串
     */
沉默王二's avatar
沉默王二 已提交
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
    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。

沉默王二's avatar
沉默王二 已提交
262
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/object-class/abstract-05.png)
沉默王二's avatar
沉默王二 已提交
263 264 265 266 267 268 269 270 271 272 273


在 resource 目录下的文件可以通过 `ClassLoader.getResource()` 的方式获取到 URI 路径,然后就可以取到文本内容了。

输出结果如下所示:

```
[hello world]
[HELLO WORLD]
```

沉默王二's avatar
沉默王二 已提交
274
### 04、抽象类总结
沉默王二's avatar
沉默王二 已提交
275 276 277

好了,对于抽象类我们简单总结一下:

沉默王二's avatar
沉默王二 已提交
278 279 280 281
- 1、抽象类不能被实例化。
- 2、抽象类应该至少有一个抽象方法,否则它没有任何意义。
- 3、抽象类中的抽象方法没有方法体。
- 4、抽象类的子类必须给出父类中的抽象方法的具体实现,除非该子类也是抽象类。
沉默王二's avatar
沉默王二 已提交
282 283 284 285 286 287

“完了吗?二哥”三妹似乎还沉浸在聆听教诲的快乐中。

“是滴,这次我们系统化的学习了抽象类,可以说面面俱到了。三妹你可以把代码敲一遍,加强了一些印象,电脑交给你了。”说完,我就跑到阳台去抽烟了。

“呼。。。。。”一个大大的眼圈飘散开来,又是愉快的一天~
沉默王二's avatar
jvm  
沉默王二 已提交
288

沉默王二's avatar
沉默王二 已提交
289 290 291 292

----


沉默王二's avatar
9000+  
沉默王二 已提交
293
GitHub 上标星 9000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 9000+ 的 Java 教程](https://javabetter.cn/overview/)
沉默王二's avatar
沉默王二 已提交
294

沉默王二's avatar
沉默王二 已提交
295 296

微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
沉默王二's avatar
沉默王二 已提交
297

沉默王二's avatar
沉默王二 已提交
298
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/gongzhonghao.png)