在Java中,抽象类(Abstract Class)是一种特殊的类,用于声明方法但不实现它们的类。抽象类不能被实例化,而是用作其他类的父类,提供一种抽象的模板或者接口,要求其子类实现特定的方法。那抽象类到底有什么用呢?
01.定义抽象类
定义抽象类的时候需要用到关键字 abstract.
abstract class AbstractPlayer {
}
可以看到类名前是以Abstract开头的,这是因为《阿里的 Java 开发手册》上有强调,“抽象类命名要使用 Abstract 或 Base 开头”,做到类名的要求:名如其意。
02.抽象类的特征
1.不能实例化
Java语法不允许直接实例化抽象类
2.抽象方法和普通方法
抽象类中既可以定义抽象方法并且抽象方法没有方法体!,也可以定义普通方法
public abstract class AbstractTeacher {
abstract void type();//子类继承后必须要实现的方法,并且要加上方法体!
public void teach() {
System.out.println("每个老师都要上课");
}
}
3.可以定义构造方法和定义成员变量:
抽象类可以有构造方法,用于初始化抽象类的成员变量或执行必要的初始化操作。子类在实例化时会调用父类的构造方法。
抽象类可以包含成员变量、静态常量和静态方法。这些成员变量和方法可以被其子类继承和访问。
abstract class Animal {
protected String name;//静态成员
public static final int MAX_AGE = 100;//静态常量
public static void makeSound();//静态方法
// 抽象类的构造方法
public Animal(String name) {
this.name = name;
}
子类如何继承父类呢?
class Dog extends Animal {
private String breed;
// 子类的构造方法,必须调用父类的构造方法
public Dog(String name, String breed) {
super(name); // 调用父类的构造方法进行初始化
this.breed = breed;
}
// 实现抽象方法,实现代码的复用
@Override
public void makeSound() {
System.out.println("Woof!");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog("Buddy", "Labrador");
dog.makeSound(); // 输出: Woof!
System.out.println("Name: " + dog.name); // 输出: Name: Buddy
System.out.println("Breed: " + dog.breed); // 输出: Breed: Labrador
}
}
03.案例(转载)
假设现在有一个文件,里面的内容非常简单,只有一个“Hello World”,现在需要有一个读取器将内容从文件中读取出来,最好能按照大写的方式,或者小写的方式来读。
这时候,最好定义一个抽象类 BaseFileReader:
/**
* 抽象类,定义了一个读取文件的基础框架,其中 mapFileLine 是一个抽象方法,具体实现需要由子类来完成
*/
abstract class BaseFileReader {
protected Path filePath; // 定义一个 protected 的 Path 对象,表示读取的文件路径
/**
* 构造方法,传入读取的文件路径
* @param filePath 读取的文件路径
*/
protected BaseFileReader(Path filePath) {
this.filePath = filePath;
}
/**
* 读取文件的方法,返回一个字符串列表
* @return 字符串列表,表示文件的内容
* @throws IOException 如果文件读取出错,抛出该异常
*/
public List<String> 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);
}
-
ilePath 为文件路径,使用 protected 修饰,表明该成员变量可以在需要时被子类访问到。
-
readFile()
方法用来读取文件,方法体里面调用了抽象方法mapFileLine()
——需要子类来扩展实现大小写的不同读取方式。
在我看来,BaseFileReader 类设计的就非常合理,并且易于扩展,子类只需要专注于具体的大小写实现方式就可以了。
小写的方式:
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();
}
}
从文件里面一行一行读取内容的代码被子类复用了。与此同时,子类只需要专注于自己该做的工作,LowercaseFileReader 以小写的方式读取文件内容,UppercaseFileReader 以大写的方式读取文件内容。
来看一下测试类 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());
}
}
在项目的 resource 目录下建一个文本文件,名字叫 helloworld.txt,里面的内容就是“Hello World”。文件的具体位置如下图所示,我用的集成开发环境是 Intellij IDEA。
在 resource 目录下的文件可以通过 ClassLoader.getResource()
的方式获取到 URI 路径,然后就可以取到文本内容了。
输出结果如下所示:
[hello world]
[HELLO WORLD]
04.抽象类总结
好了,对于抽象类我们简单总结一下:
- 抽象类不能被实例化。
- 抽象类应该至少有一个抽象方法,否则它没有任何意义。
- 抽象类中的抽象方法没有方法体。
- 抽象类的子类必须给出父类中的抽象方法的具体实现,除非该子类也是抽象类