Java抽象/接口讲解(第五节)抽象类和接口的区别
抽象类和接口是 Java 中实现抽象和多态的重要工具,它们在设计上有相似之处(都可以用于定义行为规范),但在功能、实现方式和应用场景上存在显著区别。理解这两者的区别对于编写灵活且易维护的代码至关重要。
1. 概念区别
抽象类:
- 抽象类是一个类,不能被直接实例化,用于定义子类的通用属性和行为。抽象类可以包含抽象方法(没有方法体,需要子类实现)和具体方法(已经实现的)。
- 应用场景:当你希望为一组类提供通用的功能,同时允许子类共享这些通用功能时,可以使用抽象类。
接口:
- 接口是一个完全抽象的类型,它定义了一组行为规范,而不提供任何实现(Java 8 之后接口可以包含默认实现)。实现接口的类必须实现接口中的所有方法。
- 应用场景:当你希望定义一组行为,而不关心这些行为的具体实现时,使用接口。接口主要用于多继承场景或定义类的能力。
2. 语法和结构的区别
特性 | 抽象类 | 接口 |
---|---|---|
关键字 | abstract class | interface |
方法 | 可以包含抽象方法和具体方法 | 只能包含抽象方法(Java 8 之前),可以有默认方法和静态方法 |
成员变量 | 可以有成员变量(实例变量) | 只能有常量,即 public static final 变量 |
构造方法 | 可以有构造方法 | 不能有构造方法 |
访问修饰符 | 方法和变量可以是 public 、protected 、private | 方法默认是 public abstract ,变量是 public static final |
继承 | 一个类只能继承一个抽象类 | 一个类可以实现多个接口 |
实现继承要求 | 子类可以选择实现抽象类中的部分方法 | 类必须实现接口中的所有方法 |
3. 抽象类与接口的特性对比
3.1 方法定义
- 抽象类:可以有抽象方法(需要子类实现)和具体方法(已经有方法体的实现)。
- 接口:Java 8 之前,接口中的所有方法都是抽象方法。Java 8 之后,接口可以包含默认方法(
default
,有方法体的具体实现)和静态方法。
抽象类示例:
abstract class Animal {
public abstract void makeSound(); // 抽象方法
public void sleep() { // 具体方法
System.out.println("动物正在睡觉");
}
}
接口示例:
interface Runnable {
void run(); // 抽象方法 (默认是 public abstract)
default void stop() { // Java 8 之后的默认方法
System.out.println("停止运行");
}
}
3.2 成员变量
- 抽象类:可以有成员变量(实例变量),子类可以继承和使用。
- 接口:只能有常量,即
public static final
变量,所有变量必须在定义时初始化。
抽象类示例:
abstract class Animal {
protected String name; // 成员变量
}
接口示例:
interface Flyable {
int MAX_HEIGHT = 1000; // 常量,隐式地 public static final
}
3.3 构造方法
- 抽象类:可以有构造方法,用于初始化父类的成员变量。尽管抽象类不能实例化,但子类创建对象时可以调用抽象类的构造方法。
- 接口:不能有构造方法,因为接口本质上不是类,不能被实例化。
抽象类示例:
abstract class Animal {
protected String name;
public Animal(String name) {
this.name = name; // 初始化成员变量
}
}
4. 继承与实现的区别
4.1 单继承 vs 多继承
- 抽象类:类与类之间是单继承的,一个类只能继承一个抽象类(或者普通类),不能继承多个类。
- 接口:一个类可以实现多个接口,接口提供了多继承的效果,允许类同时拥有多个行为。
示例:单继承
abstract class Animal {
public abstract void makeSound();
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("狗在叫");
}
}
示例:多接口实现
interface Runnable {
void run();
}
interface Swimmable {
void swim();
}
class Frog implements Runnable, Swimmable { // 同时实现两个接口
@Override
public void run() {
System.out.println("青蛙在跑");
}
@Override
public void swim() {
System.out.println("青蛙在游泳");
}
}
4.2 实现要求
- 抽象类:子类可以选择实现部分或全部抽象类中的方法。如果不实现全部方法,子类仍然是抽象类。
- 接口:实现接口的类必须实现接口中的所有抽象方法。否则,类必须声明为抽象类。
5. 应用场景的区别
5.1 抽象类的使用场景
- 当类之间有共性且需要共享一些实现时,可以使用抽象类。抽象类允许提供通用的功能,子类只需要重写特定的行为。
- 抽象类可以拥有成员变量,因此适合需要保存状态的场景(比如拥有共享数据的类)。
示例:通用功能的抽象类
abstract class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void sleep() {
System.out.println(name + " 正在睡觉");
}
public abstract void makeSound();
}
class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println(name + " 在汪汪叫");
}
}
5.2 接口的使用场景
- 接口适用于为类提供某种能力或行为,并且这些类可能来自不同的继承体系。例如,一个类可能需要实现多个不相关的接口,从而同时具备多个行为。
- 接口更倾向于定义一种能力,而不是对象之间的继承关系。例如,一个类可以实现
Flyable
接口表示它可以飞,或者实现Swimmable
接口表示它可以游泳。
示例:定义能力的接口
interface Flyable {
void fly();
}
interface Swimmable {
void swim();
}
class Duck implements Flyable, Swimmable {
@Override
public void fly() {
System.out.println("鸭子在飞");
}
@Override
public void swim() {
System.out.println("鸭子在游泳");
}
}
6. Java 8 引入的新特性
Java 8 之后,接口引入了一些新特性,模糊了接口和抽象类的部分边界。
6.1 默认方法(Default Method)
- 接口可以有默认方法,即
default
方法,允许接口在不破坏已有实现类的情况下扩展新的功能。默认方法提供了一个方法的默认实现,实现类可以选择重写它。interface Runnable { void run(); default void stop() { // Java 8 中的新特性 System.out.println("停止运行"); } }
6.2 静态方法(Static Method)
- 接口可以有静态方法,静态方法可以直接通过接口名调用,而不需要实现类的实例。静态方法不能被实现类重写。
interface Flyable {
static void checkWind() {
System.out.println("检查风速");
}
}
7. 总结
特性 | 抽象类 | 接口 |
---|---|---|
定义 | 用于定义类之间的共性,可以包含具体方法和抽象方法 | 完全抽象的类型,只定义行为规范,不提供实现(Java 8 后可以有默认实现) |
方法 | 可以包含具体方法和抽象方法 | 只能包含抽象方法,Java 8 之后可以有默认方法和静态方法 |
成员变量 | 可以包含实例变量 | 只能包含 public static final 常量 |
构造方法 | 可以有构造方法,用于初始化类的状态 | 不能有构造方法,因为接口不能实例化 |
继承和实现 | 一个类只能继承一个抽象类 | 一个类可以实现多个接口 |
应用场景 | 用于为一组相关类提供通用功能 | 用于为类提供能力或行为规范,适用于多继承或无关类之间共享行为 |
使用抽象类的场景:
- 当类之间有明确的继承关系,且需要共享部分实现时使用抽象类。
使用接口的场景:
- 当需要定义一组行为,且类可以来自不同的继承体系时使用接口。接口非常适合为类定义能力或行为规范。