在Java中,抽象类和接口是实现抽象的两种主要方式。它们都不能被实例化,但可以用来形成类的层次结构,并为子类提供一定程度的框架或模板。
抽象类(Abstract Class)
定义抽象类
抽象类使用abstract
关键字定义。它可以包含抽象方法(没有实现的方法)和具体方法(带有实现的方法)。如果一个类包含一个或多个抽象方法,那么这个类必须被声明为抽象的。
abstract class Animal {
// 抽象方法
abstract void makeSound();
// 具体方法
void eat() {
System.out.println("This animal eats food.");
}
}
在上述例子中,Animal
类是抽象的,并且它有一个抽象方法makeSound
。
实现抽象类
要创建一个抽象类的子类,必须提供抽象方法的具体实现。如果子类不实现父类的所有抽象方法,那么子类也必须被声明为抽象的。
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Bark bark");
}
}
在这个例子中,Dog
类实现了Animal
类的makeSound
抽象方法。
抽象类的目的
- 作为其他派生类的基类。
- 为派生类提供默认行为。
- 对象的多态性。
接口(Interface)
定义接口
接口是一个完全抽象的类,只包含抽象方法和常量(默认是public static final
)。从Java 8开始,接口也可以包含默认方法和静态方法。使用interface
关键字定义接口。
interface Animal {
// 抽象方法
void makeSound();
// Java 8 开始允许的默认方法
default void eat() {
System.out.println("This animal eats food.");
}
}
在上述例子中,Animal
是一个接口,里面定义了一个抽象方法makeSound
和一个默认方法eat
。
实现接口
类可以通过implements
关键字来实现接口。实现接口的类必须提供接口中所有声明的抽象方法的具体实现。
class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("Bark bark");
}
}
在这个例子中,Dog
类实现了Animal
接口的makeSound
方法。
接口的目的
- 为类提供一个实现的协议。
- 支持多继承的行为。
- 确保派生类遵循特定的形式。
- 提供一种插件式架构的形式,使得我们可以在运行时添加新功能。
抽象类与接口的主要区别
- 实例化:抽象类和接口都不能被实例化。
- 方法定义:抽象类可以包含具体方法,而接口只能包含抽象方法(直到Java 8添加了默认和静态方法)。
- 构造函数:抽象类可以有构造函数,而接口不能。
- 状态存储:抽象类可以有实例字段,而接口不能(它们可以有静态常量)。
- 访问修饰符:抽象类中的方法可以有任何访问修饰符,而接口中的方法默认都是公开的(public)。
- 继承:一个类只能继承一个抽象类,但可以实现多个接口。
何时使用抽象类和接口
- 如果要创建一个将由许多紧密相关的对象使用的框架,则应该使用抽象类。
- 如果要定义非紧密相关的类应实现的功能,或者你需要多继承的行为,应该使用接口。
选择使用抽象类的情况:
-
共享代码:如果你想共享代码给几个密切相关的类,你可以将这些代码放在抽象类中。比如,如果多个类有相似的方法实现,那么这些实现可以放在抽象类中作为非抽象方法。
-
非公共方法:如果你需要定义非公共访问级别的方法,比如
protected
方法,那么你需要使用抽象类,因为接口中的所有方法都是公共的。 -
状态或字段:如果你的类设计需要有状态,则应该使用抽象类。接口不能保存状态,因为它们不能有实例字段。在抽象类中,你可以有变量和常量,并为这些字段定义状态和行为。
-
构造器参数:如果你的类层次结构需要使用构造器参数,抽象类是正确的选择。接口不允许构造器。
选择使用接口的情况:
-
多继承:如果你的类需要实现多个不相关类的行为,或者不同类之间需要一个公共协议,那么使用接口。因为Java不支持多重继承,所以使用接口可以绕过这个限制。
-
模块化:接口使得你可以创建非常灵活和可扩展的代码。可以在无需修改原有类代码的情况下增加新行为。这种模块化是很多设计模式的核心,如策略模式、工厂模式等。
-
解耦:接口有助于解耦,使得实现与接口分离。这样的设计可以让你轻松更换不同的实现,只要它们遵循相同的接口。
Java 8接口的新特性:
从Java 8开始,接口引入了两个新特性:
- 默认方法:也称为扩展方法,你可以在接口中提供方法的具体实现,使用
default
关键字:
public interface Flyable {
default void fly() {
System.out.println("Flying");
}
}
- 静态方法:可以在接口中定义静态方法,并且这些方法必须有具体实现:
public interface Flyable {
static void wingCount(int wings) {
System.out.println("Wing count: " + wings);
}
}
默认方法和静态方法增强了接口的功能,允许接口有更多的灵活性和表达力。
总结:
抽象类和接口都有它们各自的使用场景,通常使用抽象类来创建紧密相关的类的通用模板,而接口则为系统提供一组协议,让不相关的类可以实现。接口也支持类似于多继承的功能,可以增加代码的灵活性和可扩展性。随着Java 8和Java 9对接口功能的扩展,接口的使用变得更加灵活,但它们的核心差异依然存在。根据你的设计需求和目标,仔细选择使用抽象类还是接口是非常重要的。