文章目录
- 背景
- Java 接口与抽象类详解
- 一、接口实现与部分实现的问题
- 问题描述
- 答案
- 解决方案
- 关键概念区分
- 类型对比表
- 二、接口中的 default 方法(第一次见到)
- 问题描述
- 答案
- 接口方法的完整规则(Java 8+)
- 为什么引入 default 方法?
- 完整示例
- 三、接口与抽象类的区别
- 核心区别总结
- 详细对比
- 1. 多继承 vs 单继承
- 2. 成员变量
- 3. 构造方法
- 4. this 和 super
- 5. 访问控制
- 使用场景示例
- 组合使用示例
- 四、选择建议
- 五、总结
- 关键要点
- 一句话总结
背景
最近在某个代码中,看到了接口B extend 接口A,其实这种写法也正常,但第一次见的是 接口B中有非抽象方法,这就和之前背的八股文"接口中只能有抽象方法"相悖,但jdk确实支持这种写法,因为第一次见到这种写法,所以特此记录下
Java 接口与抽象类详解
一、接口实现与部分实现的问题
问题描述
假设有以下代码:
// 接口定义publicinterfaceAStudentInterface{voidmethod1();voidmethod2();voidmethod3();}// 类实现接口publicclassBStudentClassimplementsAStudentInterface{@Overridepublicvoidmethod1(){// 实现了第一个方法}// method2 和 method3 没有实现}问题:BStudentClass是接口还是类?如果只实现了部分抽象方法会怎样?
答案
BStudentClass是一个类,不是接口。
如果普通类只实现了接口的部分方法,编译会报错:
错误: BStudentClass不是抽象的, 并且未覆盖AStudentInterface中的抽象方法method2()解决方案
方案一:声明为抽象类
publicabstractclassBStudentClassimplementsAStudentInterface{@Overridepublicvoidmethod1(){// 实现了一个方法}// method2 和 method3 保持抽象状态,不需要实现}方案二:实现所有方法
publicclassBStudentClassimplementsAStudentInterface{@Overridepublicvoidmethod1(){/* 实现 */}@Overridepublicvoidmethod2(){/* 实现 */}@Overridepublicvoidmethod3(){/* 实现 */}}关键概念区分
| 语法 | 关键字 | 说明 |
|---|---|---|
| 类实现接口 | implements | 类去实现接口 |
| 类继承类 | extends | 子类继承父类 |
| 接口继承接口 | extends | 子接口继承父接口 |
类型对比表
| 类型 | 关键字 | 是否需要实现所有接口方法 | 能否实例化 |
|---|---|---|---|
| 普通类 | class | ✅ 必须全部实现 | ✅ 可以 |
| 抽象类 | abstract class | ❌ 可以部分实现 | ❌ 不可以 |
| 接口 | interface | 不适用(接口定义方法签名) | ❌ 不可以 |
二、接口中的 default 方法(第一次见到)
问题描述
// 接口定义publicinterfaceAStudentInterface{voidmethod1();voidmethod2();voidmethod3();}publicinterfaceBStudentInterfaceextendsAStudentInterface{@Overridedefaultvoidmethod1(){// 具体实现}}问题:接口中不是只允许存在抽象方法吗?为什么实现了 method1 方法,还是个接口?
答案
BStudentInterface仍然是一个接口,这是合法的写法。
原因:从 Java 8 开始,接口可以包含具有具体实现的方法,称为default 方法(默认方法)。
接口方法的完整规则(Java 8+)
| 方法类型 | 关键字 | 是否有实现 | 是否必须实现 |
|---|---|---|---|
| 抽象方法 | 无(默认) | ❌ 无 | ✅ 实现类必须实现 |
| 默认方法 | default | ✅ 有 | ❌ 可选,可以覆盖 |
| 静态方法 | static | ✅ 有 | 不适用,通过接口调用 |
| 私有方法 | private(Java 9+) | ✅ 有 | 不适用,接口内部使用 |
为什么引入 default 方法?
问题场景:假设AStudentInterface被 100 个类实现了,现在想给接口新增一个方法:
publicinterfaceAStudentInterface{voidmethod1();voidmethod2();voidmethod3();voidmethod4();// ❌ 编译错误!100个实现类都要改}解决方案:使用 default 方法
publicinterfaceAStudentInterface{voidmethod1();voidmethod2();voidmethod3();defaultvoidmethod4(){System.out.println("默认实现");}}这样,已有的实现类不需要修改,都能继承这个默认实现。
完整示例
// 父接口publicinterfaceAStudentInterface{voidmethod1();voidmethod2();voidmethod3();}// 子接口:用 default 实现部分方法publicinterfaceBStudentInterfaceextendsAStudentInterface{@Overridedefaultvoidmethod1(){System.out.println("method1 的默认实现");}// method2 和 method3 仍然是抽象方法}// 实现类publicclassStudentImplimplementsBStudentInterface{@Overridepublicvoidmethod2(){System.out.println("实现 method2");}@Overridepublicvoidmethod3(){System.out.println("实现 method3");}// method1 可以直接使用默认实现,也可以覆盖}三、接口与抽象类的区别
核心区别总结
| 特性 | 接口 | 抽象类 |
|---|---|---|
| 继承关系 | 可以implements多个接口 | 只能extends一个类 |
| 成员变量 | 只能是public static final常量 | 可以有各种类型的成员变量 |
| 构造方法 | ❌ 没有 | ✅ 有 |
| 代码块 | ❌ 没有 | ✅ 可以有静态块、实例块 |
| 方法修饰符 | 默认 public | 可以是任意访问修饰符 |
| this/super | ❌ 不能使用 | ✅ 可以使用 |
| 设计理念 | 定义"能做什么"(能力) | 定义"是什么"(本质) |
详细对比
1. 多继承 vs 单继承
// ✅ 接口:可以实现多个publicclassStudentimplementsAStudentInterface,BStudentInterface,CStudentInterface{// ...}// ❌ 抽象类:只能继承一个publicclassStudentextendsAbstractStudent{// 只能一个// ...}2. 成员变量
// 接口:只能有常量publicinterfaceBStudentInterfaceextendsAStudentInterface{intCOUNT=100;// 自动是 public static final// String name; // ❌ 编译错误// private int age; // ❌ 编译错误}// 抽象类:可以有各种变量publicabstractclassAbstractStudent{privateStringname;// ✅ 私有变量protectedintage;// ✅ 受保护变量publicstaticfinalintCOUNT=100;// ✅ 常量}3. 构造方法
// 接口:没有构造方法publicinterfaceBStudentInterface{// public BStudentInterface() {} // ❌ 编译错误}// 抽象类:可以有构造方法publicabstractclassAbstractStudent{privateStringname;publicAbstractStudent(Stringname){// ✅ 构造方法this.name=name;}}4. this 和 super
// 接口:不能使用 this 和 superpublicinterfaceBStudentInterface{defaultvoidmethod1(){// this.toString(); // ❌ 不允许// super.toString(); // ❌ 不允许System.out.println("default");}}// 抽象类:可以使用 this 和 superpublicabstractclassAbstractStudent{publicvoidmethod1(){this.toString();// ✅ 可以super.toString();// ✅ 可以}}5. 访问控制
// 接口:方法默认是 publicpublicinterfaceBStudentInterface{voidmethod1();// 自动是 public// private void method2(); // ❌ Java 8 不支持// protected void method3(); // ❌ 不支持}// 抽象类:灵活的访问控制publicabstractclassAbstractStudent{publicvoidmethod1(){}// ✅ publicprotectedvoidmethod2(){}// ✅ protectedprivatevoidmethod3(){}// ✅ privateabstractvoidmethod4();// ✅ 包私有}使用场景示例
// 场景:定义"飞行能力"// ✅ 用接口:因为飞行是一种能力,不是本质publicinterfaceFlyable{defaultvoidfly(){System.out.println("默认飞行方式");}}// 场景:定义"学生"的基础属性// ✅ 用抽象类:因为学生是本质,有共同的状态和行为publicabstractclassAbstractStudent{privateStringname;// 状态privateintage;publicAbstractStudent(Stringname,intage){this.name=name;this.age=age;}publicvoidsleep(){// 具体行为System.out.println(name+" is sleeping");}publicabstractvoidstudy();// 抽象行为}组合使用示例
// 抽象类:定义本质 + 共享状态publicabstractclassAbstractStudent{protectedStringname;publicAbstractStudent(Stringname){this.name=name;}publicabstractvoidstudy();}// 接口:定义能力publicinterfaceFlyable{defaultvoidfly(){System.out.println("飞行");}}publicinterfaceSwimable{defaultvoidswim(){System.out.println("游泳");}}// 具体类:继承抽象类 + 实现多个接口publicclassSuperStudentextendsAbstractStudentimplementsFlyable,Swimable{publicSuperStudent(Stringname){super(name);}@Overridepublicvoidstudy(){System.out.println(name+" 正在学习");}}四、选择建议
| 场景 | 推荐 |
|---|---|
| 需要定义多个不相关类的共同行为 | 用接口 |
| 需要共享状态(成员变量) | 用抽象类 |
| 需要多继承 | 只能用接口 |
| 需要构造方法初始化 | 用抽象类 |
| 只是定义能力/契约 | 用接口 |
| 是一种本质/身份 | 用抽象类 |
五、总结
关键要点
- 普通类实现接口:必须实现所有抽象方法,否则编译报错
- 抽象类实现接口:可以只实现部分方法
- Java 8 default 方法:让接口可以提供默认实现,增强向后兼容性
- 接口 vs 抽象类:
- 接口 = 契约 + 能力(能做什么)
- 抽象类 = 本质 + 模板(是什么,共享状态和行为)
一句话总结
Java 8 的 default 方法只是让接口更灵活,并没有改变接口"定义能力"的本质。如果需要共享状态和构造逻辑,还是要用抽象类。