一. 面试题及剖析
1. 今日面试题
在上一篇文章中,壹哥 给大家讲解了对面向对象的理解,在面向对象里面,其实会涉及到很多内容,比如类、接口、抽象类等内容,所以面试官在这里就会问到相关问题,比如:
请说说Java中抽象类和接口有什么区别?
2. 题目剖析
这个题目,在面试初级Java程序员、实习生时,出现的频率还是挺高的。这道题目考察的还是与面向对象相关的知识点,具体是在考察我们对接口和抽象类的掌握细节,主要以记忆为主,这道题目还是比较容易回答的。对于抽象类和接口,一方面我们要记住两者的区别,另一方面我们还要明确两者各自的使用场景。
二. 参考答案
对于抽象类和接口的区别,其实没有太多深层的东西需要我们去探究,更多的还是以记忆为主,这里 壹哥 给各位设计了一个表格,清晰的对比了两者的区别。
1. 抽象类和接口常规区别
| 参数 | 抽象类 | 接口 |
| 使用场景 | 抽象类用于捕捉子类中的通用特性 | 接口是抽象方法的集合 |
| 默认的方法实现 | 可以有默认的方法实现 | 接口完全是抽象的。Java8之前接口方法没有默认实现,Java8开始接口方法也可以有默认实现 |
| 实现 | 子类使用extends关键字来继承抽象类。如果子类不是抽象类,它需要实现抽象类中所有声明的抽象方法。 | 子类使用implements关键字来实现接口,它需要实现接口中所有声明的未实现方法。 |
| 构造方法 | 抽象类可以有构造方法 | 接口不能有构造方法 |
| 与正常Java类的区别 | 抽象类不能实例化,只能用作子类的超类,其余方面和普通Java类没有任何区别 | 接口只是一种形式,接口自身不做任何事情 |
| 访问修饰符 | 抽象方法可以有public、protected和default这些修饰符,但不能用private来修饰 | 接口方法默认的修饰符是public,不可以使用其它修饰符。 |
| main方法 | 抽象类中可以有main方法,并且我们可以运行 | 接口中也可以有main方法,但一般不会在接口中编写main方法。 |
| static静态方法 | 抽象类中可以有static静态方法 | Java8及之后,接口中也可以有static静态方法 |
| 多继承 | 抽象类可以继承一个类和实现多个接口 | 接口可以继承一个或多个其它接口 |
| 执行速度 | 抽象类比接口速度快 | 接口稍微有点慢的,因为它需要时间去匹配在子类中实现的具体方法。 |
| 添加新方法 | 如果在抽象类中添加了新的方法,我们可以在抽象类中给该方法提供默认的实现,因此不需要改变子类中的代码。 | 如果在接口中添加方法,那么实现了该接口的子类也需要改变代码。 |
以上表格中就是抽象类与接口的主要区别,我们面试时,挑拣几个重要的区别说说就可以了。
2. 抽象类和接口使用场景的区别
我们再次从抽象类和接口的使用场景方面,来分析对比两者之间的区别。
- 如果我们想在多个子类中都做同一件事,即抽取出子类的共同特性,但这件事每个类中的具体实现细节又不同时,就可以考虑使用抽象类。比如我们每个人都要找一个对象,但是每个人找的对象又不一样。
- 如果我们想实现多继承,就必须使用接口,因为Java不支持多继承,子类不能继承多个父类,但可以实现多个接口。
3. Java单继承的原因
在上面的小节中,我提到了Java中的类是单继承、多实现的,那为什么这样呢?
这是因为,当子类重写父类方法的时候,或者隐藏父类的成员变量以及静态方法的时候,JVM会使用不同的绑定规则。如果一个类有多个直接的父类,那么会使绑定规则变得更复杂。为了简化软件的体系结构和绑定机制,所以Java语言禁止多继承。
而接口却可以多继承,是因为接口中只有抽象方法,没有静态方法和非常量的属性,只有接口的实现类才会重写接口中方法。因此一个类有多个接口也不会增加JVM的绑定机制和复杂度。
4. 从设计目的分析两者的区别
接着我们再从接口和抽象类的设计目的来分析,两者之间有什么区别,其实这个角度更接近于两者的本质区别。
接口的设计目的,更多的是对类的行为进行约束。因为接口中不能规定子类不可以有什么行为,也就是说接口其实是提供了一种机制,可以强制要求不同的类具有相同的行为。接口约束了行为的有无,但不对如何实现行为进行限制。
抽象类的设计目的,既可以提高代码的复用性,又能达到个性化的目的。当不同的类具有某些相同的行为,且其中一部分行为的实现方式一致时,可以让这些类都继承自一个抽象类。比如我们可以在抽象类中实现所有子类都具有的共同方法A,避免让所有的子类都来实现这个A方法,这就达到了代码复用的目的。然后这时还可以在抽象类中定义一个抽象方法B,留给各个子类自己去实现,这样又达到了个性化的目的。
三. 代码案例
为了方便各位加深对接口和抽象类区别的理解,壹哥 给各位编写了一个案例,希望能对你有所启发。
我们首先来设计一个产品需求,比如有这么一个例子:
每个人都具有 eat() 、sleep() 两个功能方法,这时候我们其实可以分别通过接口和抽象类来定义这个抽象概念。
//接口
public interface IPerson {
void eat();
void sleep();
}
//抽象类
public abstract class AbstractPerson {
public abstract void eat();
public abstract void sleep();
}
但是如果我们现在需要让人拥有一项特殊的技能——开飞机 drivePlane(),这时如何增加这个行为呢?首先我们思考一下:
- 将开飞机这个方法与前面两个方法一同写入到抽象类中,你觉得这样合适吗?如果是这样,但凡继承这个抽象类的人都具有了开飞机的技能,明显不合适吧。
- 将开飞机的方法与前面两个方法一同写入到接口中,当需要使用开飞机功能的时候,也必须实现接口中的eat()、sleep()方法,这显然也不合适。
那么该如何解决这个问题呢 ? 我们可以仔细想一想,eat和sleep都是人类本身所具有的一种行为,而开飞机这种行为则是后天训练出来的,只能算是对人类的一种附加或者延伸, 两者不应该在同一个范畴内。所以我们考虑将这个单独的行为,独立的设计一个接口,其中包含drivePlane()方法,可以将Person设计为一个抽象类, 其中包括eat()、sleep() 方法。
所以根据上面的需求,壹哥 把代码更改如下:
//接口中定义特殊功能
public interface IPerson {
void drivePlane();
}
//抽象类中还是和之前一样
public abstract class AbstractPerson {
public abstract void eat();
public abstract void sleep();
}
//定义子类拥有特殊功能
public class Pilot extends AbstractPerson implements IPerson{
@Override
public void eat() {
System.out.println("飞行员开始吃...");
}
@Override
public void sleep() {
System.out.println("飞行员开始睡...");
}
@Override
public void drivePlane() {
System.out.println("飞行员开飞机...");
}
}
通过上面的这个案例,你现在是否明白什么时候该用抽象类,什么时候该用接口了吗?其实我们只需要明白,继承属于 "是不是" 的关系,而 接口 实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则该类必定是这个抽象类的同一种类;而接口实现则是有没有、具备不具备的关系,比如人是否具备开飞机的能力,能则可以实现这个接口,不能就不实现这个接口。
三. 结论
我在上面给各位讲解了抽象类和接口的各种区别,并且通过代码案例讲解了该在什么情况下选择抽象类和接口,最后我再给各位简单梳理一下两者的相同点及不同点。
1. 接口和抽象类的共同点如下:
- 两者都属于上层的抽象层;
- 两者都不能被实例化;
- 两者都能包含抽象的方法,这些抽象的方法都用于描述类具备的功能,但是不必提供具体的实现。
2. 接口和抽象类的区别如下:
1. 在抽象类中可以有非抽象方法,从而避免在子类中重复书写这些方法,这样可以提高代码的复用性,这是抽象类的优势;Java8之前,接口中只能有抽象的方法,Java8之后接口中也可以有默认实现。
2. 一个类只能继承一个直接父类,这个父类可以是具体的类也可是抽象类;但是一个类可以实现多个接口。
......
- 两者都属于上层的抽象层;
- 两者都不能被实例化;
- 两者都能包含抽象的方法,这些抽象的方法都用于描述类具备的功能,但是不必提供具体的实现。
1. 在抽象类中可以有非抽象方法,从而避免在子类中重复书写这些方法,这样可以提高代码的复用性,这是抽象类的优势;Java8之前,接口中只能有抽象的方法,Java8之后接口中也可以有默认实现。
2. 一个类只能继承一个直接父类,这个父类可以是具体的类也可是抽象类;但是一个类可以实现多个接口。......
至此,壹哥 就给各位总结了接口与抽象类的区别,不知道你是否还有别的见解呢?可以在评论区留言讨论哦。



