相同点
- 提供通用实现:都可以为子类或实现类提供一些通用的代码实现,避免重复编写。例如,在处理一些基础的数据处理逻辑时,抽象类和接口默认方法都可以提供相应实现。
- 支持多态:子类或实现类都可以通过重写方法来实现不同的行为,实现多态性。比如一个绘图程序,不同图形类(继承抽象图形类或实现图形接口)通过重写绘制方法来实现各自的绘制逻辑。
不同点
- 定义方式:
- 抽象类:使用
abstract class
关键字定义,它可以包含抽象方法和具体方法,还能有成员变量、构造函数等。例如:
abstract class Shape {
protected String color;
public Shape(String color) {
this.color = color;
}
public abstract void draw();
public void printColor() {
System.out.println("Color: " + color);
}
}
- **接口**:使用 `interface` 关键字定义,Java 8 之前只能有抽象方法,Java 8 引入默认方法和静态方法后,接口可包含具体实现的方法,但接口中不能有成员变量(除了 `public static final` 类型),也没有构造函数。例如:
interface Drawable {
void draw();
default void setColor(String color) {
System.out.println("Setting color in Drawable: " + color);
}
}
- 继承关系:
- 抽象类:一个类只能继承一个抽象类,因为 Java 不支持多重继承。例如,
Circle
类继承 Shape
抽象类:
class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public void draw() {
System.out.println("Drawing a circle with color " + color + " and radius " + radius);
}
}
- **接口**:一个类可以实现多个接口,这使得类可以从多个来源获取行为。例如,`Rectangle` 类实现 `Drawable` 和 `Serializable` 接口:
class Rectangle implements Drawable, Serializable {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public void draw() {
System.out.println("Drawing a rectangle with width " + width + " and height " + height);
}
}
- 访问修饰符:
- 抽象类:抽象类中的方法可以有各种访问修饰符,如
public
、protected
、private
等。
- 接口:接口中的方法默认是
public
和 abstract
的,默认方法默认是 public
的,不能使用其他访问修饰符(除了内部类)。
选择使用接口默认方法还是抽象类的理由
- 使用接口默认方法的场景:
- 需要多重实现:当一个类需要从多个不同来源获取行为时,使用接口。比如一个
SmartPhone
类既需要实现 Callable
接口用于打电话功能,又需要实现 Camera
接口用于拍照功能。
- 对已有类添加新功能:如果要给一组不相关的类添加一个新的行为,接口默认方法是更好的选择。例如,要给多个不同类型的类添加一个通用的日志记录功能,不需要修改这些类的继承结构,只需让它们实现包含默认日志记录方法的接口即可。
- 使用抽象类的场景:
- 存在公共状态和行为:当一组相关的类有共同的状态(成员变量)和行为,并且这些类之间有明显的继承关系时,使用抽象类。例如,在一个游戏开发中,不同的角色类(战士、法师等)都继承自一个抽象的
Character
类,该抽象类包含生命值、攻击力等公共状态和一些公共行为方法。
- 强制子类实现某些方法:如果希望强制子类实现某些方法,同时又提供一些默认实现,抽象类更合适。因为抽象类可以定义抽象方法,要求子类必须实现,同时也能提供具体方法的默认实现。