我最近接受了两次电话采访,被问及接口类和抽象类之间的区别。我已经解释了我能想到的每一个方面,但似乎他们在等我提一些具体的事情,我不知道是什么。

根据我的经验,我认为以下是正确的。如果我遗漏了一个要点,请告诉我。

接口:

接口中声明的每个方法都必须在子类中实现。接口中只能存在事件、委托、财产(C#)和方法。一个类可以实现多个接口。

抽象类:

子类只能实现抽象方法。抽象类可以具有具有实现的普通方法。除了事件、委托、财产和方法之外,抽象类还可以有类变量。由于C#中不存在多重继承,一个类只能实现一个抽象类。

在这之后,面试官提出了一个问题:“如果你有一个只有抽象方法的抽象类呢?这和接口有什么不同?”我不知道答案,但我认为这是上面提到的继承,对吧?另一位面试官问我,“如果你在接口中有一个公共变量,那会和抽象类中有什么不同?”我坚持认为你不能在接口中使用公共变量。我不知道他想听什么,但他也不满意。

另请参阅:

何时使用接口而不是抽象类,反之亦然接口与抽象类如何决定使用抽象类和接口?接口和抽象类之间的区别是什么?


当前回答

tl;博士当您看到“Is A”关系时,请使用继承/抽象类。当您看到“有”关系时,创建成员变量。当您看到“依赖于外部提供程序”时,实现(而不是继承)一个接口。

面试问题:接口和抽象类有什么区别?你如何决定何时使用什么?我主要得到以下一个或全部答案:答案1:你不能创建抽象类和接口的对象。

ZK(这是我的首字母缩写):你不能创建任何一个对象。所以这并不是什么区别。这是接口和抽象类之间的相似之处。反问:为什么不能创建抽象类或接口的对象?

答案2:抽象类可以有一个函数体作为部分/默认实现。

ZK:反问题:所以如果我将其更改为纯抽象类,将所有虚拟函数标记为抽象,并且不为任何虚拟函数提供默认实现。这会使抽象类和接口相同吗?之后它们可以互换使用吗?

答案3:接口允许多重继承,抽象类不允许。

ZK:反问:你真的从接口继承吗?还是只实现一个接口,从抽象类继承?实现和继承之间有什么区别?这些反问题会让考生们措手不及,让大多数人挠头,或者直接进入下一个问题。这让我觉得人们需要帮助来构建面向对象编程的基本构件。原始问题和所有反问题的答案都可以在英语和UML中找到。为了更好地理解这两个结构,您必须至少了解以下内容。

共同名词:共同名词是指同一类或同一种类的事物的“共同”名称。例如水果、动物、城市、汽车等。

专有名词:专有名词是物体、地点或事物的名称。苹果、猫、纽约、本田雅阁等。

汽车是一个通用名词。本田雅阁是一个专有名词,可能是一个合成专有名词,一个由两个名词组成的专有名词。

来到UML部分。您应该熟悉以下关系:

是A有A使用

让我们考虑以下两句话。-本田雅阁是一辆车?-本田雅阁有车吗?

哪一个听起来正确?简单的英语和理解能力。本田雅阁(HondaAccord)和汽车(Cars)有着“是A”的关系。本田雅阁(Honda accord)没有车,它“是”一辆车。本田雅阁“有一个”音乐播放器。

当两个实体共享“Is A”关系时,它更适合继承。Has a relationship是创建成员变量的更好候选。这样,我们的代码如下所示:

abstract class Car
{
   string color;
   int speed;
}
class HondaAccord : Car
{
   MusicPlayer musicPlayer;
}

现在本田不生产音乐播放器。或者至少这不是他们的主要业务。

因此,他们联系其他公司并签订合同。如果你在这里接收到电源和这两条电线上的输出信号,它在这些扬声器上会播放得很好。

这使得Music Player成为界面的完美候选。你不在乎谁为它提供支持,只要连接正常即可。

您可以用索尼或其他方式替换LG的MusicPlayer。这不会改变本田雅阁的任何事情。

为什么不能创建抽象类的对象?

因为你不能走进展厅说给我一辆车。你必须提供一个专有名词。什么车?可能是本田雅阁。这就是销售代理可以给你东西的时候。

为什么不能创建接口的对象?因为你不能走进展厅说给我一份音乐播放器的合同。这没用。接口位于消费者和提供者之间,只是为了促成协议。你将如何处理协议副本?它不会播放音乐。

为什么接口允许多重继承?

接口未被继承。接口已实现。界面是与外部世界交互的候选界面。本田雅阁有一个加油接口。它有给轮胎充气的接口。和用来给足球充气的软管一样。因此,新代码如下所示:

abstract class Car
{
    string color;
    int speed;
}
class HondaAccord : Car, IInflateAir, IRefueling
{
    MusicPlayer musicPlayer;
}

英文的意思是“本田雅阁是一款支持轮胎充气和加油的汽车”。

其他回答

虽然这个问题很老,但我想补充一点,支持接口:

可以使用任何依赖注入工具来注入接口,其中作为抽象类注入的支持很少。

接口是执行特定行为的轻量级方式。这是一种想法。

遗产考虑一辆汽车和一辆公共汽车。它们是两种不同的车辆。但是,它们仍然有一些共同的财产,比如它们有方向盘、制动器、齿轮、发动机等。因此,使用继承概念,这可以表示如下。。。

public class Vehicle {
    private Driver driver;
    private Seat[] seatArray; //In java and most of the Object Oriented Programming(OOP) languages, square brackets are used to denote arrays(Collections).
    //You can define as many properties as you want here ...
}

现在是自行车。。。

public class Bicycle extends Vehicle {
    //You define properties which are unique to bicycles here ...
    private Pedal pedal;
}

还有一辆车。。。

public class Car extends Vehicle {
    private Engine engine;
    private Door[] doors;
}

这就是关于继承的全部内容。我们使用它们将对象分类为更简单的Base表单及其子表单,如上文所示。

抽象类

抽象类是不完整的对象。为了进一步理解它,让我们再次考虑车辆类比。可以驾驶车辆。正确的但不同的车辆以不同的方式行驶。。。例如,你不能像驾驶自行车一样驾驶汽车。那么如何表示车辆的驱动功能呢?很难检查它是什么类型的车辆,并使用它自己的功能驾驶它;在添加新类型的车辆时,您必须反复更改驾驶员类别。这里是抽象类和方法的作用。您可以将驱动方法定义为抽象的,以告知每个继承的子级都必须实现此函数。所以如果你修改了车辆等级。。。

//......Code of Vehicle Class
abstract public void drive();
//.....Code continues

Bicycle和Car还必须指定如何驾驶它。否则,代码将无法编译并引发错误。简言之抽象类是具有一些不完整函数的部分不完整类,继承的子类必须指定自己的函数。

接口接口完全不完整。他们没有任何财产。他们只是表明继承的孩子有能力做某事。。。假设你随身携带不同类型的手机。他们每个人都有不同的方式来完成不同的功能;打电话给某人。手机制造商指定了如何操作。在这里,手机可以拨打一个号码,也就是说,它是可拨打的。让我们将其表示为一个接口。

public interface Dialable {
    public void dial(Number n);
}

在这里,Dialable的制作者定义了如何拨号。你只需要给它一个号码。

// Makers define how exactly dialable work inside.

Dialable PHONE1 = new Dialable() {
    public void dial(Number n) {
        //Do the phone1's own way to dial a number
    }
}

Dialable PHONE2 = new Dialable() {
    public void dial(Number n) {
        //Do the phone2's own way to dial a number
    }
}


//Suppose there is a function written by someone else, which expects a Dialable
......
public static void main(String[] args) {
    Dialable myDialable = SomeLibrary.PHONE1;
    SomeOtherLibrary.doSomethingUsingADialable(myDialable);
}
.....

通过使用接口而不是抽象类,使用Dialable的函数的编写者无需担心其财产。它有触摸屏或拨号盘吗?是固定固定电话还是移动电话。你只需要知道它是否可以拨号;它是否继承(或实现)可拨号接口。

更重要的是,如果有一天你将可拨号电话换成另一个

......
public static void main(String[] args) {
    Dialable myDialable = SomeLibrary.PHONE2; // <-- changed from PHONE1 to PHONE2
    SomeOtherLibrary.doSomethingUsingADialable(myDialable);
}
.....

您可以确定代码仍然完美运行,因为使用可拨号接口的功能不(也不可能)依赖于可拨号接口中指定的细节以外的细节。它们都实现了可拨号接口,这是函数唯一关心的事情。

开发人员通常使用接口来确保对象之间的互操作性(可互换使用),只要它们共享一个共同的功能(就像您可以换成座机或移动电话,只要您只需要拨打一个号码)。简而言之,接口是抽象类的一个简单得多的版本,没有任何财产。此外,请注意,您可以实现(继承)任意多的接口,但只能扩展(继承)单个父类。

更多信息抽象类与接口

接口:

不包含含义接口可以从多个接口继承(支持多重继承)成员自动公开可以包含财产、方法、事件和索引器

抽象类:

可能/可能不包含实施;至少有一个成员不会被执行。类可以从单个基类继承;不允许多重继承。成员具有访问修饰符可以包含字段、财产、构造函数、析构函数、方法、事件和索引器

这些是抽象类和接口之间的关键区别。

如果您认为java是OOP语言来回答这个问题,那么Java8的发布会导致上述答案中的一些内容过时。现在,java接口可以具有具有具体实现的默认方法。

Oracle网站提供了接口和抽象类之间的关键区别。

如果出现以下情况,请考虑使用抽象类:

您希望在几个密切相关的类之间共享代码。您希望扩展抽象类的类具有许多公共方法或字段,或者需要公共以外的访问修饰符(如protected和private)。您要声明非静态或非final字段。

如果出现以下情况,请考虑使用接口:

您希望不相关的类实现您的接口。例如,许多不相关的对象可以实现Serializable接口。您希望指定特定数据类型的行为,但不关心谁实现了它的行为。您希望利用类型的多重继承。

简单来说,我想使用

接口:通过多个不相关的对象实现合同

抽象类:在多个相关对象之间实现相同或不同的行为

看看代码示例,以清楚的方式理解事情:我应该如何解释接口和抽象类之间的区别?