前言
前面我们学习了 java 集合中的 hashmap、arraylist、linkedlist。
这一篇回到 java 面向对象基础中非常经典的问题:接口和抽象类有什么区别?
这个问题在面试中非常常见,尤其是 java 初中级面试,经常会问:
- 什么是接口?
- 什么是抽象类?
- 接口和抽象类有什么区别?
- 什么时候用接口,什么时候用抽象类?
- 一个类可以实现多个接口吗?
- 一个类可以继承多个抽象类吗?
- jdk 8 之后接口有什么变化?
很多人会背:
接口是 implements,抽象类是 extends;接口可以多实现,抽象类只能单继承。
这只是其中一部分。
真正回答时,还要从设计目的、成员变量、方法、构造方法、继承关系、使用场景等角度说清楚。
一、先给结论
可以先看一张表:
| 对比点 | 接口 | 抽象类 |
|---|---|---|
| 关键字 | interface | abstract class |
| 实现/继承 | implements | extends |
| 多继承关系 | 一个类可以实现多个接口 | 一个类只能继承一个抽象类 |
| 设计目的 | 定义规范、能力 | 抽取共性、代码复用 |
| 普通成员变量 | 默认是 public static final 常量 | 可以有普通成员变量 |
| 构造方法 | 没有构造方法 | 可以有构造方法 |
| 抽象方法 | 可以有 | 可以有 |
| 普通方法 | jdk 8 后可以有默认方法和静态方法 | 可以有普通方法 |
| 适合场景 | 定义行为规范 | 多个子类有共同属性和方法 |
简单说:
接口更像是一套规范,表示“能做什么”。
抽象类更像是父类模板,表示“是什么,并且具备一些共性”。
比如:
会飞:接口 动物:抽象类
鸟是动物,同时会飞。
飞机不是动物,但也会飞。
所以“会飞”更适合用接口,“动物”更适合用抽象类。
二、什么是抽象类?
抽象类使用 abstract class 定义。
如果一个类中有抽象方法,那么这个类必须定义为抽象类。
示例:
public abstract class animal {
private string name;
public animal(string name) {
this.name = name;
}
public abstract void eat();
public void sleep() {
system.out.println(name + "正在睡觉");
}
public string getname() {
return name;
}
}
这里:
public abstract void eat();
是抽象方法。
抽象方法只有方法声明,没有方法体。
public void sleep() {
system.out.println(name + "正在睡觉");
}
是普通方法,有具体实现。
抽象类中既可以有抽象方法,也可以有普通方法。
三、抽象类如何使用?
抽象类不能直接创建对象。
错误写法:
animal animal = new animal("动物");
因为 animal 是抽象类,不能直接实例化。
它需要由子类继承,并实现抽象方法。
public class cat extends animal {
public cat(string name) {
super(name);
}
@override
public void eat() {
system.out.println(getname() + "正在吃鱼");
}
}
测试:
public class test {
public static void main(string[] args) {
animal cat = new cat("小猫");
cat.eat();
cat.sleep();
}
}
输出:
小猫正在吃鱼 小猫正在睡觉
这里体现了抽象类的作用:
- 抽取共同属性:
name - 抽取共同方法:
sleep - 定义必须由子类实现的方法:
eat
四、什么是接口?
接口使用 interface 定义。
示例:
public interface flyable {
void fly();
}
这里定义了一个“会飞”的能力。
任何类只要实现这个接口,就必须实现 fly 方法。
public class bird implements flyable {
@override
public void fly() {
system.out.println("鸟在飞");
}
}
飞机也可以实现这个接口:
public class plane implements flyable {
@override
public void fly() {
system.out.println("飞机在飞");
}
}
测试:
public class test {
public static void main(string[] args) {
flyable bird = new bird();
flyable plane = new plane();
bird.fly();
plane.fly();
}
}
输出:
鸟在飞 飞机在飞
可以看到,接口更强调“能力”或“规范”。
bird 和 plane 类型完全不同,但它们都具备 fly 这个能力。
五、接口和抽象类的设计目的不同
这是最关键的区别。
1. 抽象类:抽取共性
抽象类更适合描述一类事物的共同特征。
比如:
public abstract class animal {
private string name;
private integer age;
public abstract void eat();
public void sleep() {
system.out.println("睡觉");
}
}
猫、狗、老虎都属于动物。
它们都有:
name age sleep()
但是吃东西的方式不同,所以 eat 可以定义为抽象方法。
这种情况就适合用抽象类。
2. 接口:定义规范
接口更适合描述某种能力。
比如:
public interface payable {
void pay();
}
支付宝支付、微信支付、银行卡支付都可以实现这个接口。
public class alipay implements payable {
@override
public void pay() {
system.out.println("支付宝支付");
}
}
public class wechatpay implements payable {
@override
public void pay() {
system.out.println("微信支付");
}
}
这里接口定义的是支付规范,具体怎么支付由实现类决定。
六、继承关系不同
java 是单继承的。
也就是说,一个类只能继承一个父类或抽象类。
public class cat extends animal {
}
但是一个类可以实现多个接口。
public class bird extends animal implements flyable, runnable {
}
这表示:
bird 是一种 animal bird 具备 flyable 能力 bird 具备 runnable 能力
这也是接口非常重要的作用:
java 类只能单继承,但可以通过多个接口扩展多种能力。
七、成员变量区别
接口中的变量默认是:
public static final
也就是常量。
比如:
public interface config {
int max_size = 100;
}
等价于:
public static final int max_size = 100;
所以接口中的变量必须赋值,不能作为普通成员变量使用。
抽象类中可以有普通成员变量:
public abstract class animal {
private string name;
private integer age;
}
这些属性可以由子类继承和使用。
所以如果你需要保存对象状态,比如姓名、年龄、创建时间,更适合使用抽象类。
八、构造方法区别
接口没有构造方法。
因为接口不是类的对象模板,不能直接实例化,也不保存普通对象状态。
抽象类可以有构造方法。
public abstract class animal {
private string name;
public animal(string name) {
this.name = name;
}
}
子类构造方法中可以调用父类构造方法:
public class dog extends animal {
public dog(string name) {
super(name);
}
}
这里说明抽象类虽然不能直接创建对象,但它仍然是一个类,可以参与子类对象的初始化过程。
九、方法区别
1. 抽象类中的方法
抽象类中可以有:
- 抽象方法
- 普通方法
- 静态方法
- final 方法
- private 方法
比如:
public abstract class animal {
public abstract void eat();
public void sleep() {
system.out.println("睡觉");
}
public static void info() {
system.out.println("动物信息");
}
}
2. 接口中的方法
在早期 java 中,接口中主要是抽象方法。
jdk 8 之后,接口可以有:
- 抽象方法
- 默认方法
- 静态方法
示例:
public interface flyable {
void fly();
default void stop() {
system.out.println("停止飞行");
}
static void info() {
system.out.println("飞行能力");
}
}
这里:
default void stop()
是默认方法,实现类可以直接使用,也可以重写。
static void info()
是接口静态方法,可以通过接口名调用:
flyable.info();
jdk 9 之后,接口中还可以有私有方法,用来复用接口内部逻辑。
不过初学和面试中,重点掌握 jdk 8 的默认方法和静态方法就够了。
十、接口默认方法有什么用?
jdk 8 之后接口支持默认方法,主要是为了解决接口升级问题。
假设有一个接口:
public interface userservice {
void save();
}
已经有很多类实现了它。
现在想给接口新增一个方法:
void delete();
如果直接加抽象方法,那么所有实现类都必须立刻实现 delete,否则会编译报错。
为了兼容旧实现类,可以使用默认方法:
public interface userservice {
void save();
default void delete() {
system.out.println("默认删除逻辑");
}
}
这样旧实现类即使不重写 delete,也不会报错。
这就是默认方法的一个重要作用。
十一、实际开发中的例子
1. 抽象类适合模板复用
比如有多个导出任务,它们都有共同流程:
查询数据 生成文件 上传文件 记录日志
可以写一个抽象类:
public abstract class abstractexportservice {
public void export() {
list<string> data = querydata();
string file = generatefile(data);
upload(file);
log();
}
protected abstract list<string> querydata();
protected string generatefile(list<string> data) {
return "导出文件";
}
protected void upload(string file) {
system.out.println("上传文件:" + file);
}
protected void log() {
system.out.println("记录导出日志");
}
}
子类只需要实现不同的数据查询逻辑:
public class empexportservice extends abstractexportservice {
@override
protected list<string> querydata() {
return arrays.aslist("员工1", "员工2");
}
}
这种场景抽象类很合适,因为它既有共同流程,又有部分方法需要子类实现。
2. 接口适合定义能力
比如支付接口:
public interface payservice {
void pay(bigdecimal amount);
}
不同支付方式实现它:
public class alipayservice implements payservice {
@override
public void pay(bigdecimal amount) {
system.out.println("支付宝支付:" + amount);
}
}
public class wechatpayservice implements payservice {
@override
public void pay(bigdecimal amount) {
system.out.println("微信支付:" + amount);
}
}
调用方只依赖接口:
public class orderservice {
private payservice payservice;
public orderservice(payservice payservice) {
this.payservice = payservice;
}
public void createorder(bigdecimal amount) {
payservice.pay(amount);
}
}
这样以后新增银行卡支付,只需要新增实现类,不需要大改调用方代码。
十二、什么时候用接口,什么时候用抽象类?
可以这样判断:
1. 优先考虑接口的场景
如果你想表达的是一种能力或规范,用接口。
比如:
可支付 可飞行 可序列化 可比较 可运行
这些都适合接口。
java 中很多经典接口也是这样:
runnable comparable serializable list map
接口更适合做解耦。
2. 优先考虑抽象类的场景
如果多个类之间有明显的共同属性、共同方法,或者有一套共同流程需要复用,可以考虑抽象类。
比如:
动物都有姓名和年龄 导出任务都有共同执行流程 模板方法模式中的父类 多个服务类有共用逻辑
这种情况下抽象类更合适。
3. 实际开发中常见组合
很多时候,接口和抽象类会一起使用。
比如:
public interface handler {
void handle();
}
然后提供一个抽象基础类:
public abstract class abstracthandler implements handler {
protected void log() {
system.out.println("记录日志");
}
}
具体实现类:
public class userhandler extends abstracthandler {
@override
public void handle() {
log();
system.out.println("处理用户逻辑");
}
}
这种写法很常见:
接口定义规范,抽象类提供部分通用实现。
十三、面试标准回答
如果面试官问:
接口和抽象类有什么区别?
可以这样回答:
接口使用 interface 定义,抽象类使用 abstract class 定义。一个类可以实现多个接口,但只能继承一个抽象类。
从设计目的上看,接口更偏向定义规范或能力,比如某个类具备支付、飞行、运行等能力;抽象类更偏向抽取共同属性和共同方法,适合多个子类之间有明显共性或者需要复用公共代码的场景。
从成员上看,接口中的变量默认是 public static final 常量,不能保存普通对象状态;抽象类可以有普通成员变量。接口没有构造方法,抽象类可以有构造方法。jdk 8 之后接口可以有默认方法和静态方法,但抽象类在普通方法、成员变量、构造方法等方面仍然更像一个父类模板。
实际开发中,如果是定义规范,优先使用接口;如果是抽取共性和复用代码,可以使用抽象类。有时候也会接口和抽象类一起使用,接口定义规范,抽象类提供通用实现。
十四、常见问题总结
1. 抽象类可以没有抽象方法吗?
可以。
一个类即使没有抽象方法,也可以定义为抽象类。
public abstract class baseservice {
public void log() {
system.out.println("记录日志");
}
}
这样做的目的通常是不希望这个类被直接实例化,而是只作为父类使用。
2. 接口可以有普通方法吗?
jdk 8 之后,接口可以有默认方法:
default void method() {
// 默认实现
}
也可以有静态方法:
static void method() {
// 静态方法
}
3. 抽象类可以实现接口吗?
可以。
public abstract class abstractservice implements service {
}
抽象类可以实现接口,但不一定要实现接口中的所有方法。
未实现的方法可以交给子类实现。
4. 接口可以继承接口吗?
可以。
而且接口可以继承多个接口。
public interface c extends a, b {
}
5. 一个类可以继承多个抽象类吗?
不可以。
java 类是单继承,一个类只能继承一个父类。
6. 一个类可以实现多个接口吗?
可以。
public class myclass implements a, b, c {
}
这也是接口解决多能力扩展的重要方式。
十五、实际开发建议
实际开发中可以记住下面几点:
- 定义规范优先用接口
- 抽取共同属性和公共逻辑用抽象类
- 不要为了用抽象类而强行设计继承关系
- java 中类只能单继承,所以抽象类要谨慎使用
- 接口更适合解耦和扩展
- 抽象类更适合模板方法和公共逻辑复用
- jdk 8 后接口可以有默认方法,但不要把接口写得太重
- 很多框架中会同时使用接口和抽象类:接口定规范,抽象类给默认实现
十六、总结
这一篇主要学习了接口和抽象类的区别。
接口更强调规范和能力,一个类可以实现多个接口;抽象类更强调共性抽取和代码复用,一个类只能继承一个抽象类。
接口中的变量默认是常量,没有构造方法;抽象类可以有普通成员变量和构造方法。jdk 8 之后,接口可以有默认方法和静态方法,但抽象类在复用公共状态和公共逻辑方面依然更强。
面试回答时,不要只背 implements 和 extends,更重要的是讲清楚它们的设计目的:接口定义规范,抽象类抽取共性。
到此这篇关于java八股之接口和抽象类有什么区别的文章就介绍到这了,更多相关java接口和抽象类区别内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论