抽象类 (Abstract Class)
一、抽象类的基本概念
1. 抽象方法
-
定义 :一个没有方法体的方法(即只有方法声明,没有具体的实现)。
-
格式:
java
public abstract 返回值类型 方法名(参数列表); -
特点:
-
使用
abstract关键字修饰。 -
没有方法体,直接以分号
;结束。 -
它定义了一个 "必须实现"的规范或契约。
-
2. 抽象类
-
定义 :包含抽象方法的类必须被声明为抽象类。
-
格式:
java
public abstract class 类名 { // 类的内容 } -
特点:
-
使用
abstract关键字修饰类。 -
抽象类不能直接实例化 (不能
new抽象类)。
-
二、抽象类的作用与设计思想
抽象类的核心是一种 "未完成"的类设计模板。
-
抽取共性,定义规范:
-
当在父类中提取多个子类的共性行为时,如果无法(或不适合)确定该行为的具体实现 ,就将该方法声明为抽象方法。
-
这样,抽象类就定义了一个 "规范"或"契约" ,强制所有非抽象的子类必须按照这个声明来提供具体的实现。
-
-
强制子类重写:
- 子类继承抽象类后,必须处理这些抽象方法。这保证了代码的一致性。
通俗理解 :抽象类就像是制定了一份 "必须完成的任务清单"。清单上只写了任务名称和要求(抽象方法),但没写怎么做。子类就像不同的执行者,它们必须按照清单完成所有任务(实现所有抽象方法),但可以根据自己的情况选择不同的完成方式。
三、继承抽象类的注意事项
当一个普通类继承一个抽象类时,有两种选择:
-
重写所有抽象方法(推荐,也是最常见的情况)
-
将父类中定义的所有抽象方法都提供具体的实现。
-
这样,子类就成为了一个 "具体类",可以被正常实例化。
-
-
将自己也声明为抽象类
-
如果子类没有重写 或没有重写全部 的抽象方法,那么这个子类也必须 用
abstract关键字修饰,成为一个新的抽象类。 -
这个新的抽象类可以继续被其他类继承。
-
简单总结 :"抽象"具有传递性。一个类只要继承了抽象方法而没有实现它,它自己就必须保持抽象。
四、抽象类的其他特性
-
抽象类中可以有构造方法:
-
虽然抽象类不能直接实例化,但它可以有构造方法。
-
构造方法用于子类创建对象时,初始化从父类继承来的成员。
java
public abstract class Animal { private String name; // 抽象类可以有构造方法 public Animal(String name) { this.name = name; } public String getName() { return name; } public abstract void eat(); } -
-
抽象类中不一定有抽象方法:
-
一个类被
abstract修饰,它就是抽象类。 -
但抽象类不一定 包含抽象方法。这种设计通常是为了防止这个类被实例化,而只允许它被继承。
java
public abstract class MyBaseClass { // 没有抽象方法,但类依然是抽象的 public void normalMethod() { System.out.println("这是一个普通方法"); } } // 这个类不能 new MyBaseClass(),只能被继承。 -
-
抽象类中可以包含普通成员:
-
抽象类可以有普通的成员变量 和普通的成员方法(非抽象方法)。
-
这些普通成员可以直接被子类继承和使用。
-
五、完整示例
java
// 1. 定义抽象类
public abstract class Animal {
private String name;
private int age;
// 抽象类可以有构造方法
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
// 抽象方法:定义了一个必须完成的行为规范
public abstract void eat();
// 普通方法:子类可以直接继承使用
public void sleep() {
System.out.println(name + "正在睡觉");
}
// Getter/Setter
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
// 2. 具体子类:必须重写所有抽象方法
public class Cat extends Animal {
public Cat(String name, int age) {
super(name, age); // 调用父类构造方法
}
@Override
public void eat() {
System.out.println(getName() + "(" + getAge() + "岁)正在吃鱼");
}
}
// 3. 另一个具体子类
public class Dog extends Animal {
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(getName() + "(" + getAge() + "岁)正在啃骨头");
}
// 子类可以有自己的特有方法
public void watchHouse() {
System.out.println(getName() + "在看家");
}
}
// 4. 测试类
public class Test {
public static void main(String[] args) {
// Animal a = new Animal(); // 错误!抽象类不能实例化
Animal cat = new Cat("小花", 2);
cat.eat(); // 输出:小花(2岁)正在吃鱼
cat.sleep(); // 输出:小花正在睡觉
Dog dog = new Dog("大黄", 3);
dog.eat(); // 输出:大黄(3岁)正在啃骨头
dog.watchHouse(); // 输出:大黄在看家
}
}
// 5. 抽象子类示例(不重写抽象方法)
public abstract class Bird extends Animal {
// 没有重写 eat() 方法,所以 Bird 也必须是抽象类
public Bird(String name, int age) {
super(name, age);
}
// 可以定义新的抽象方法
public abstract void fly();
}
// 6. 最终的具体类,需要实现所有抽象方法(包括Animal的和Bird的)
public class Sparrow extends Bird {
public Sparrow(String name, int age) {
super(name, age);
}
@Override
public void eat() { // 实现 Animal 的 eat()
System.out.println(getName() + "正在吃虫子");
}
@Override
public void fly() { // 实现 Bird 的 fly()
System.out.println(getName() + "正在飞翔");
}
}
六、抽象类 vs 普通类 vs 接口
| 特性 | 普通类 | 抽象类 | 接口 |
|---|---|---|---|
| 实例化 | 可以直接实例化 | 不能直接实例化 | 不能直接实例化 |
| 方法 | 全是具体方法 | 可以有具体方法和抽象方法 | Java 8前全是抽象方法,之后可以有默认/静态方法 |
| 构造方法 | 有 | 有 | 没有 |
| 成员变量 | 任意类型 | 任意类型 | 只能是 public static final 常量 |
| 继承/实现 | 单继承 | 单继承 | 多实现 |
| 设计目的 | 具体实现 | 模板设计,部分实现,部分规范 | 纯规范,定义行为契约 |
七、总结
-
抽象类是一个"半成品"类,它定义了部分实现,也声明了部分必须由子类完成的规范(抽象方法)。
-
核心作用 :代码复用 + 强制规范。既提取了共性代码,又强制子类必须实现特定行为。
-
使用场景:当多个相关类有共同的属性和行为,但某些行为的具体实现各不相同时。
-
设计理念 :"面向抽象编程",依赖于抽象(父类或接口)而非具体实现,提高了代码的灵活性和可扩展性。
java
package abstract02;
public class Test {
public static void main(String[] args) {
Frog f = new Frog(1, "wawa");
System.out.println(f.getName()+", "+f.getAge());
f.drink();
f.eat();
f.swim();
Dog d = new Dog(5, "wangwang");
System.out.println(d.getName()+", "+d.getAge());
d.drink();
d.eat();
d.swim();
Sheep s = new Sheep(6, "miemie");
System.out.println(s.getName()+", "+s.getAge());
s.drink();
s.eat();
}
}
package abstract02;
public abstract class Animal {
private String name;
private int age;
public Animal() {
}
public Animal(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void drink(){
System.out.println("动物在喝水");
}
public abstract void eat();
}
package abstract02;
public class Dog extends Animal implements Swim{
public Dog() {
}
public Dog(int age, String name) {
super(age, name);
}
@Override
public void eat() {
System.out.println("狗在吃骨头");
}
@Override
public void swim() {
System.out.println("狗在游泳");
}
}
package abstract02;
public class Frog extends Animal implements Swim{
public Frog() {
}
public Frog(int age, String name) {
super(age, name);
}
@Override
public void eat() {
System.out.println("青蛙在吃虫子");
}
@Override
public void swim(){
System.out.println("青蛙在游泳");
}
}
package abstract02;
public class Sheep extends Animal{
public Sheep() {
}
public Sheep(int age, String name) {
super(age, name);
}
@Override
public void eat() {
System.out.println("🐏在吃草");
}
}
package abstract02;
public interface Swim {
public abstract void swim();
}