我曾参加一次面试,面试官首先问我抽象类中所有方法都是抽象的和接口之间的区别。
我回答说,如果将来需要继承一些东西,如果你已经扩展了一个类,那么你就无法这样做。
然后,他说这是一种情况,在这种情况下,你永远不必扩展任何其他类,而且你必须实现一个合同。在这种情况下,抽象类或接口哪个更好?
我告诉他你可以使用其中任意一个,但他并不满意。我不明白为什么 - 我认为这是开发者/设计者的选择。
我曾参加一次面试,面试官首先问我抽象类中所有方法都是抽象的和接口之间的区别。
我回答说,如果将来需要继承一些东西,如果你已经扩展了一个类,那么你就无法这样做。
然后,他说这是一种情况,在这种情况下,你永远不必扩展任何其他类,而且你必须实现一个合同。在这种情况下,抽象类或接口哪个更好?
我告诉他你可以使用其中任意一个,但他并不满意。我不明白为什么 - 我认为这是开发者/设计者的选择。
认为接口代表一个合同的答案是不可接受的。这是我们给初学者的答案,因为对于没有太多架构经验和没有阅读过很多经典书籍的人来说,清楚地了解抽象类和接口本质上的区别可能会太复杂。
任何带有public
方法的抽象类都像接口一样代表一个合同。
99%情况下,不提供任何实现的抽象类表示对象的角色。而接口则代表一个角色。每个对象可能具有不同的角色,这些角色不应被绑定在一起,而是由相关对象组成。
我用这个例子来说明:
你的面试官可能会说:
我有一个可以走路的机器人
和一个可以走路的人类
。
所以基于这种情况,他问你:我应该将行走功能提取到一个抽象基类或接口中,知道这两个实现没有任何共同之处?
你想... "噢,我知道了:在这种情况下,拥有一个带有抽象方法walk()
的抽象类,那么与声明一个具有walk()
方法的接口完全相同。" 所以你的答案肯定会是:"这是开发人员的选择!",而这确实不总是一个有效的答案。
为什么?让我们看看下一个期望:
人类
可以吃饭,但显然机器人
不能也不需要。
如果使用抽象类来实现行走功能,你最终会得到:
public abstract class Biped {
public void abstract walk();
}
public Robot extends Biped {
public void walk() {
//walk at 10km/h speed
}
}
public Human extends Biped {
public void walk() {
//walk at 5km/h speed
}
}
你如何解决“进食”功能的问题?你卡住了,因为你无法在Biped基类中实现它,因为这会违反Liskov替换原则,因为机器人不吃饭!而且由于已知的Java规则,你也不能指望Human扩展另一个基类。
当然,你可以添加一个专门针对Human的Feedable接口:
public interface Feedable {
void eat();
}
签名变为:public Human extends Biped implements Feedable {
显然,将一个角色通过类实现,将另一个角色通过接口实现是毫无意义和令人困惑的。
这就是为什么每当我们有选择的时候,从接口开始通常更受欢迎。
通过接口,我们可以轻松地通过组合来建模角色。
因此最终解决方案如下:
public interface Walkable {
void abstract walk();
}
public interface Feedable {
void eat();
}
public Robot implements Walkable {
public void walk() {
//walk at 10km/h speed
}
}
public Human implements Walkable, Feedable {
public void walk() {
//walk at 5km/h speed
}
public void eat(){
//...
}
}
这难道不让你想起了接口隔离原则?;)
总之,如果您要指定一个IS-A关系,请使用抽象类。 如果您意识到您要建模一个Role(比如说IS-CAPABLE-OF关系),请使用接口。
interface Base{}
interface Sub1 extends Base{}
interface Sub2 extends Base{}
interface SubSub extends Sub1, Sub2{}
使用抽象类是不可能实现同样的功能的:
abstract class Base{}
abstract class Sub1 extends Base{}
abstract class Sub2 extends Base{}
// NOT ALLOWED! can only extend one class
// abstract class SubSub extends Sub1, Sub2{}
这在C++中是允许的(尽管难以正确实现)。我想他可能正在钓鱼。一般来说,这就是为什么我总是尝试编写接口层次结构而不是类层次结构的终极原因。
我看到这个问题已经有答案了。顺便说一下,我想分享一下我认为迄今为止读过的最好的解释。
下面的文本是从Deshmukh,Hanumant的书中复制并粘贴的。OCP Oracle Certified Professional Java SE 11程序员I考试基础1Z0-815:通过OCP Java 11开发人员认证第1部分考试1Z0-815的学习指南(第319页)。Enthuware. Kindle版。
13.2区分类继承和接口继承,包括抽象类13.2.1接口和抽象类之间的区别☝
“接口和抽象类有什么区别”通常是Java技术面试中的第一个问题。虽然这是一个简单的破冰问题,但它也是评估候选人对面向对象编程理解的一个很好的问题。候选人通常会开始背诵技术上的差异,例如接口不能有方法实现(自Java 8以来可以),而抽象类可以。接口不能有静态方法(自Java 8以来可以)或实例字段,而抽象类可以等等。这一切都是正确的,但并不真正令人印象深刻。接口和抽象类之间的根本区别在于接口只定义行为。接口告诉你关于实际对象的信息除了它的行为之外一无所知。另一方面,抽象类定义了一个对象,进而驱动其行为。如果您理解了这个概念,那么它们的其他所有内容都会落到位。例如,“移动”是各种对象(如汽车、猫或股票价格)显示的行为。这些对象除了移动之外没有任何共同点。换句话说,如果您获得一个“移动”的对象,您不会知道您要处理什么样的对象。它可能是汽车、猫或股票价格。如果您想在Java中捕获此行为,则可以使用名为Movable的接口和名为move()的方法。另一方面,如果您谈论汽车,则立即在您的头脑中形成了一个对象的图像。您可以感觉到汽车将是具有引擎、轮子和移动功能的东西。您直观地知道股票价格或猫不能成为汽车,即使它们都会移动。抽象类正是为此目的而设计的,一旦您确定了一个概念对象,您就不需要担心其行为。行为自然流动。如果您创建了一个名为Automobile的抽象类,则几乎可以确定它将具有move、turn、accelerate或brake等方法。它将具有用于捕获内部细节的字段,例如Engine、Wheels和Gears。只需说出汽车这个词,您就可以得到所有这些信息。从上面的讨论中,应该清楚地看出接口和抽象类是不可互换的。即使没有非抽象方法的抽象类在功能上看起来与接口相似,但两者在根本上是不同的。如果您要捕获行为,则必须使用接口。如果您要捕获概念对象,则必须使用抽象类。接口是创建合同的自然方式,因为它们强制您实现它们定义的方法。
除此之外,在您想要向类添加新接口的情况下,您可以实现尽可能多的接口。