个人精简总结
在Java面向对象编程(OOP)中,抽象类和接口是实现抽象的两种主要工具,它们在定义上有着根本的区别,同时在适用场景上各有特点。
接口 (Interface)
定义了行为契约
抽象方法:默认隐式包含 public abstract 修饰符(写不写都有),无方法体,分号结束。重点: 类实现接口时,必须具体实现所有接口定义的方法
成员变量:(修正点) 接口中一直都可以定义变量,但它们默认且只能是 public static final 的(即常量)。这并非 Java 8 的新特性,而是接口诞生之初就有的规则。
版本演进:
Java 8:引入了 static 方法和 default 方法(默认方法),允许在接口内提供具体实现。
Java 9:引入了 private 方法,用于在接口内部复用代码,不对外暴露。
关系:一个类可以实现多个接口,接口可以继承(extends)多个其他接口,但接口不能继承类(包括抽象类)。
抽象类 (Abstract Class)
为了代码复用和扩展
状态维护:可以有成员变量(各种访问权限均可)、构造函数(虽然不能直接 new 实例化,但供子类初始化父类状态使用),用于维护类的实例状态。
方法灵活:可以包含 abstract 方法(强制子类实现)和非 abstract 方法(提供通用实现)。
继承限制:一个类只能继承一个抽象类(单继承),但抽象类本身可以实现多个接口。
1. 抽象类(Abstract Class)的概念与应用
1.1 概念
抽象类是用 abstract 关键字声明的类,不能被实例化,即不能直接用 new 关键字创建抽象类的对象。抽象类可以包含构造方法,可以定义成员变量和常量,也可以包含方法,其中一些方法是不带有具体实现的,称为抽象方法。子类继承抽象类后,必须实现抽象方法,否则该子类也必须声明为抽象类。
1.2 实际应用场景
需要一个公共基类,部分方法有具体实现,部分方法需要子类实现 :抽象类特别适合在需要定义一组相关类的共同基类时使用。例如,假设有一个表示图形的基类
Shape,它包含计算面积和周长的抽象方法,但每种具体的图形如圆形、矩形等,有不同的计算方式。通过抽象类可以统一定义这些抽象方法,而由具体的子类进行实现。需要包含成员变量和构造方法 :抽象类可以包含属性和构造方法,这在需要在多个子类之间共享状态和初始化逻辑时非常有用。
需要提供部分方法的默认实现 :抽象类可以提供一些方法的默认实现,不需要子类再次实现这些方法,即使子类需要可以选择是否覆盖这些方法。
1.3 示例代码
abstract class Shape {
protected double width;
protected double height;
public Shape(double width, double height) {
this.width = width;
this.height = height;
}
public abstract double area();
public abstract double perimeter();
}
class Circle extends Shape {
public Circle(double radius) {
super(radius, radius);
}
@Override
public double area() {
return 3.14 * width * width;
}
@Override
public double perimeter() {
return 2 * 3.14 * width;
}
}
class Rectangle extends Shape {
public Rectangle(double width, double height) {
super(width, height);
}
@Override
public double area() {
return width * height;
}
@Override
public double perimeter() {
return 2 * (width + height);
}
}2. 接口(Interface)的概念与应用
2.1 概念
接口是用 interface 关键字声明的,表示一种抽象的规范或协议。实际上,接口定义了一个类必须实现的方法集合。这些方法在接口中没有具体的实现,必须由实现该接口的类(实现类)来实现。在Java 8之前,接口中的所有方法都必须是抽象的,且不能包含成员变量。Java 8之后,接口中可以添加默认方法和静态方法,这增加了接口的灵活性。
2.2 实际应用场景
定义一个公共的契约或协议 :接口特别适合用来定义一个公共的协议或数据类型。所有实现该接口的类都必须遵守这个协议,即必须实现接口中的抽象方法。
实现多多继承 :一个类可以实现多个接口,但最多只能继承一个抽象类。这样,通过接口,Java实现了多多继承的一种形式。
服务的解耦 :接口可以用于解耦服务的定义和实现,这在软件设计中有着重要的意义。例如,在依赖注入和AOP编织中,接口可以定义服务的契约,具体的实现则由不同类完成。
Java 8新增特性 :接口中可以添加默认方法和静态方法。这使得接口更加灵活,可以在不破坏已有实现类的情况下扩展功能。
2.3 示例代码
interface Shape {
double area();
double perimeter();
// 在Java 8中,可以添加默认方法
default void draw() {
System.out.println("默认绘制");
}
// 也可以添加静态方法
static void calculateSomething() {
System.out.println("执行静态方法");
}
}
class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return 3.14 * radius * radius;
}
@Override
public double perimeter() {
return 2 * 3.14 * radius;
}
// 重写默认方法(可选)
@Override
public void draw() {
System.out.println("绘制一个圆");
}
}
class Rectangle implements Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double area() {
return width * height;
}
@Override
public double perimeter() {
return 2 * (width + height);
}
}3. 抽象类与接口的主要区别
通过上面的讨论,可以总结出抽象类与接口之间的主要区别:
实现方式 :
抽象类可以同时包含抽象方法和具體方法。
接口在Java 8之前,所有方法都是抽象方法;在Java 8及以后,可以有默认方法和静态方法。
类的继承 :
一个类只能继承一个抽象类,不能多继承抽象类。
一个类可以实现多个接口。
状态的持有 :
抽象类可以有成员变量,维护一定的状态。
接口在Java 8之前不能有成员变量,Java 8以后可以有静态变量,但通常不用于维护状态。
设计目的 :
抽象类主要用于代码的重用,提供一个共同的基类,让子类在此基础上扩展或实现特定的方法。
接口主要用于定义一个公共的协议或契约,所有实现该接口的类必须遵守这个协议,接口常用于声明方法的集合,而不关心具体的实现。
4. 应用场景对比
使用抽象类 :
需要一个共同的基类,部分方法需要在子类中实现,部分方法已经有具体实现。
需要包含成员变量和构造方法,维护一定的状态。
需要提供部分方法的默认实现,而子类可以选择是否覆盖这些方法。
使用接口 :
需要定义一个公共的协议或契约,方法的具体实现由不同的类负责。
需要实现多多继承,即一个类需要继承多个不同的方法集合。
需要在Java 8及以后版本,接口中增加默认方法和静态方法以扩展功能。
5. 总结
抽象类和接口是Java面向对象编程中的两种重要机制,分别用于实现不同的抽象需求。抽象类更适合在需要一个公共基类且需要包含部分具体实现和状态的情况;而接口更适合在需要定义一个公共的契约或协议,实现多多继承的情况下使用。选择抽象类还是接口,要根据具体的问题和需求,深思熟虑地进行设计,以确保系统的灵活性、可扩展性和可维护性。
通过实践和不断的学习,我们可以更好地掌握抽象类和接口的使用方法,提升我们的编程技能和软件设计能力。
在 Java 接口(Interface)中,成员变量的定义规则非常严格。简单来说,接口中只能定义常量,不能定义实例变量。
具体规则如下:
1. 核心规则:public static final
接口中的所有成员变量默认且只能是 public(公开的)、static(静态的)和 final(不可变的)。
哪怕你省略了这些修饰符,Java 编译器也会自动隐式地加上它们。
2. 详细特性
- 必须初始化: 由于是
final的,变量在声明时必须同时赋值,不能先声明后赋值。 - 全局共享: 由于是
static的,该变量属于接口本身,被所有实现该接口的类共享。 - 不可修改: 由于是
final的,一旦赋值,其值不能被改变。 - 访问权限: 由于是
public的,任何地方只要能访问该接口,就能访问该变量。
3. 代码示例
以下三种写法在 Java 编译器眼中是完全等效的:
public interface MyInterface {
// 写法 1:完整写法(推荐用于理解,但不推荐在实际代码中这样冗余地写)
public static final int CONNECT_TIMEOUT = 1000;
// 写法 2:省略部分修饰符
static final String DEFAULT_NAME = "Admin";
// 写法 3:最常用写法(完全省略修饰符)
int MAX_RETRY = 3;
// 编译器会自动将其视为:public static final int MAX_RETRY = 3;
}4. 什么是被禁止的?
在接口中定义成员变量时,以下情况会导致编译错误:
private或protected:接口是用于被实现的契约,变量必须对实现类可见,所以只能是public。- 未初始化:例如
int x;是错误的,必须写成int x = 0;。 - 试图修改值:在实现类中不能执行
MyInterface.MAX_RETRY = 5;。
5. 为什么这样设计?
接口的设计初衷是定义行为规范(Methods),而不是定义对象的状态(Instance Variables)。
- 如果允许接口定义实例变量(非
static),那么接口就变成了抽象类,这就破坏了 Java 不支持多继承状态(State)的原则。 - 因此,接口只能包含不依赖于具体对象的全局常量。
6.具体案例
1. 为什么 public abstract String type(); 特意标明 abstract?
在抽象类中声明一个方法为 abstract,强制子类实现:抽象方法要求所有非抽象子类必须提供具体实现。这样可以确保子类不会遗漏重要的方法实现。
抽象类中非 abstract,子类可以选择是否覆盖这个方法,而不是强制要求实现。
2. 一个类不仅继承了抽象类也实现了接口,这两者是什么关系?
public class ApplicationStartEventListenerExecute
extends AbstractApplicationExecute
implements ApplicationListener<ApplicationStartedEvent>这表示:
继承抽象类 (
extends AbstractApplicationExecute):- 继承了父类的成员变量和方法实现
- 必须实现父类的抽象方法 ([type()](file:///D:/Java_projects/damai/damai-spring-cloud-framework/damai-service-initialize/src/main/java/com/damai/initialize/execute/ApplicationStartEventListenerExecute.java#L45-L47))
- 可以覆盖父类的非final方法
实现接口 (
implements ApplicationListener<ApplicationStartedEvent>):- 必须实现接口中定义的所有方法 (如 [onApplicationEvent()](file:///D:/Java_projects/damai/damai-spring-cloud-framework/damai-service-initialize/src/main/java/com/damai/initialize/execute/ApplicationStartEventListenerExecute.java#L39-L41))
- 接口只是定义了契约,不提供任何实现
3. 对成员属性和成员函数的复用区别
继承抽象类的复用:
- 可以复用父类的成员变量
- 可以复用父类的具体方法实现
- 可以覆盖父类的方法
实现接口的复用:
- 接口不能包含实例变量(除了常量)
- 接口只能定义方法签名,不提供实现(Java 8之后可以有默认实现)
- 实现类必须提供接口方法的具体实现