抽象类中所有方法都是抽象的与接口有什么区别?

9

我曾参加一次面试,面试官首先问我抽象类中所有方法都是抽象的和接口之间的区别。

我回答说,如果将来需要继承一些东西,如果你已经扩展了一个类,那么你就无法这样做。

然后,他说这是一种情况,在这种情况下,你永远不必扩展任何其他类,而且你必须实现一个合同。在这种情况下,抽象类或接口哪个更好?

我告诉他你可以使用其中任意一个,但他并不满意。我不明白为什么 - 我认为这是开发者/设计者的选择。


你可以实现许多接口。 - Leo
一个抽象类可以有构造函数,而接口则不行。但是,你必须实现那个构造函数,所以我不确定你是否可以将构造函数本身定义为抽象的... - Mattias Buelens
@leo 我知道,但如果我不需要在实现或扩展其他功能的类中进行任何其他修改,那么我应该选择什么? 接口还是抽象类?为什么? - Dangling Piyush
1
只是一条信息,在Java 8中,您可以在接口中添加默认方法及其实现。在我看来,为了合同(握手)实现接口。 - Mani
1
通常当面试官问这种问题时,他们心中已经有一个具体的东西——基本上是一个关键词,他们希望你提到。这可能就是为什么当你说了一件事情(多重继承)时,面试官说你应该忽略它(换句话说,“那是一个正确的答案,但不是我想要的答案”)。因此,如果不知道面试官在想什么,很难回答这个问题。听起来像是面试官提出了一个糟糕的问题。 - yshavit
显示剩余2条评论
7个回答

13

认为接口代表一个合同的答案是不可接受的。这是我们给初学者的答案,因为对于没有太多架构经验和没有阅读过很多经典书籍的人来说,清楚地了解抽象类和接口本质上的区别可能会太复杂。

任何带有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关系),请使用接口。


1
这里是区别:
  1. 一个类可以继承仅一个抽象类,但可以实现任意数量的接口。
  2. 抽象类可以具有受保护的、私有的(不适用于您的问题)、包和公共方法,但接口只能具有公共方法。
  3. 抽象类可以有实例变量(通常称为数据成员或属性),而接口只能有静态变量。
回答问题:“从未扩展过某个协定”,答案是:“如果我需要实例变量和/或非公共方法,我会使用抽象类,否则我会使用接口”。

我同意,但接口隔离原则怎么办?http://en.wikipedia.org/wiki/Interface_segregation_principle 这与面试官的看法不符。 - Dangling Piyush

0
需要记住的一件事是接口具有钻石继承的能力。
考虑以下接口层次结构:
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++中是允许的(尽管难以正确实现)。我想他可能正在钓鱼。一般来说,这就是为什么我总是尝试编写接口层次结构而不是类层次结构的终极原因。


0

我看到这个问题已经有答案了。顺便说一下,我想分享一下我认为迄今为止读过的最好的解释。

下面的文本是从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。只需说出汽车这个词,您就可以得到所有这些信息。从上面的讨论中,应该清楚地看出接口和抽象类是不可互换的。即使没有非抽象方法的抽象类在功能上看起来与接口相似,但两者在根本上是不同的。如果您要捕获行为,则必须使用接口。如果您要捕获概念对象,则必须使用抽象类。

0

接口是创建合同的自然方式,因为它们强制您实现它们定义的方法。

除此之外,在您想要向类添加新接口的情况下,您可以实现尽可能多的接口。


但是第一部分(强制你实现方法)也适用于抽象方法,而第二部分是面试官明确表示应该忽略的情况。 - yshavit
假设您将来不需要任何其他实现,那么您会选择接口还是抽象类? - Dangling Piyush
@yshavit 确实 :). 也许面试官考虑到了可访问性,在接口中所有方法必须是public。这更像是一份合同。但是..抽象方法也可以声明为public :) - perencia

0
我不能确定你的面试官想要什么,但是接口更像是一个“契约”,而抽象基类虽然也可以扮演这个角色,但更适用于层次结构或IS-A关系。例如,苹果是水果,梨子是水果等等。但你说得对,在那种情况下它们可以互换使用,但OO纯粹主义者可能不想使用抽象类,除非他们正在表达IS-A关系。

啊,那不是这样的。让我们举个例子,一个继承Thread类的类就是一个线程,而一个实现Runnable接口的类则强制进行直接实现...这取决于开发者的选择...对吧...但如果Thread是一个只有run方法为抽象的抽象类,那么你会选择什么? - Dangling Piyush
实现Runnable和扩展Thread之间有一个小区别。实现Runnable时,您使用线程来运行它,例如组合。扩展Thread时,您使用is-a来扩展/自定义线程的行为(如果需要)。我会实现Runnable。这里是另一个关于它的线程(请原谅双关语):[https://dev59.com/onRB5IYBdhLWcg3wtZMV]。 - Steve Harrington

0
对于第一种情况,我会选择使用接口而不是抽象类,并将所有方法都声明为抽象的,因为使用接口可以让我在未来的实现类中扩展其他(抽象)类。
对于第二种情况,如果你真的不想让你的具体类继承任何其他类,并且还想要“实现”契约,那么你可以使用所有方法都声明为抽象的抽象类。

我应该选择什么? - Dangling Piyush
我认为你所被问到的问题更像是一个讨论的开始,而不是一个词语回答——接口或抽象类。你应该回答在哪种情况下使用什么。 - Jay
一个问题必须有正确的答案,这是我一直以来的想法,除非它是NP-hard问题。http://en.wikipedia.org/wiki/NP-hard - Dangling Piyush
如果不是面试的话,我会同意你的观点。面试中的每个问题并不总是关于正确或错误的答案,而是关于了解候选人的分析能力、应变能力、解决问题的态度。我可能在这里错了,因为我对你的面试了解很少。所以得到答案的最佳人选只有面试官。在我看来,对于这种开放式的问题,没有一个完美的答案。 - Jay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接