在我的一次采访中,我被要求解释接口类和抽象类之间的区别。

以下是我的回答:

Methods of a Java interface are implicitly abstract and cannot have implementations. A Java abstract class can have instance methods that implements a default behaviour. Variables declared in a Java interface are by default final. An abstract class may contain non-final variables. Members of a Java interface are public by default. A Java abstract class can have the usual flavours of class members like private, protected, etc. A Java interface should be implemented using keyword “implements”; A Java abstract class should be extended using keyword “extends”. An interface can extend another Java interface only, an abstract class can extend another Java class and implement multiple Java interfaces. A Java class can implement multiple interfaces but it can extend only one abstract class.

然而,面试官并不满意,他告诉我这种描述代表了“书本知识”。

他让我给出一个更实际的回答,用实际的例子解释我什么时候会选择抽象类而不是接口。

我哪里错了?


当前回答

我将尝试用实际场景来回答,以说明两者之间的区别。

接口是零负载的,即不需要维护状态,因此将契约(能力)与类关联是更好的选择。

例如,说我有一个执行一些操作的任务类,现在在单独的线程中执行一个任务,我不需要扩展线程类,更好的选择是使任务实现可运行的接口(即实现其run()方法),然后将此任务类的对象传递给线程实例并调用其start()方法。

现在你可以问,如果Runnable是一个抽象类呢?

从技术上讲,这是可能的,但从设计角度来看,这是一个糟糕的选择原因:

Runnable没有与之相关的状态,也没有“提供”任何状态 run()方法的默认实现 Task必须扩展它,因此它不能扩展任何其他类 Task没有提供任何专门化到Runnable类,它所需要的只是重写run()方法

换句话说,Task类需要在线程中运行的能力,这是通过实现Runnable接口而实现的,而扩展thread类则使其成为线程。

简单地把我们的接口定义为一种能力(契约),而使用 的抽象类,用于定义的框架(公共/部分)实现 它。

免责声明:下面是愚蠢的例子,尽量不要判断:-P

interface Forgiver {
    void forgive();
}

abstract class GodLike implements Forgiver {
    abstract void forget();
    final void forgive() {
        forget();
    }
}

现在你可以选择成为神一样的人,但你可以选择只成为宽恕者(即不成为神一样的人),并做:

class HumanLike implements Forgiver {
    void forgive() {
       // forgive but remember    
    }
}

或者你可以选择像上帝一样去做:

class AngelLike extends GodLike {
    void forget() {
       // forget to forgive     
    }
}

P.S.与java 8接口也可以有静态以及默认(可重写实现)方法,因此区别b/w接口和抽象类甚至更窄。

其他回答

接口是一个“契约”,其中实现契约的类承诺实现方法。举个例子,当我将一款游戏从2D升级到3D时,我不得不编写一个界面而不是类。我必须创建一个界面来共享2D和3D版本的游戏类别。

package adventure;
import java.awt.*;
public interface Playable {
    public void playSound(String s);
    public Image loadPicture(String s);    
}

然后我可以实现基于环境的方法,同时仍然能够从一个不知道正在加载的游戏版本的对象调用这些方法。

公共类Adventure扩展了JFrame实现了Playable

公共类Dungeon3D扩展了SimpleApplication实现的Playable

公共类Main扩展了SimpleApplication实现了AnimEventListener ActionListener,播放

通常,在游戏世界中,世界可以是一个抽象类,在游戏中执行方法:

public abstract class World...

    public Playable owner;

    public Playable getOwner() {
        return owner;
    }

    public void setOwner(Playable owner) {
        this.owner = owner;
    }

下面是一个围绕Java 8的解释,试图展示抽象类和接口之间的关键区别,并涵盖Java助理考试所需的所有细节。

关键概念:

一个类只能扩展一个类,但它可以实现任意数量的接口 接口定义了类的功能,抽象类定义了它是什么 抽象类是类。它们不能被实例化,但在其他方面表现得像普通类 两者都可以有抽象方法和静态方法 接口可以有默认方法和静态final常量,也可以扩展其他接口 所有接口成员都是公共的(直到Java 9)

接口定义了类的功能,抽象类定义了它是什么

每罗迪格林:

接口通常用来描述一个类的能力,而不是它的中心标识,例如,一个Automobile类可能实现了可回收接口,它可以应用于许多不相关的对象。抽象类定义其后代的核心标识。如果你定义一个Dog抽象类,那么达尔马提亚的后代就是Dog,他们不仅仅是可狗的。

Pre Java 8, @Daniel Lerps的回答非常准确,接口就像实现类必须履行的契约。

现在,使用默认方法,它们更像一个Mixin,仍然执行契约,但也可以提供代码来完成这项工作。这使得接口可以接管抽象类的一些用例。

抽象类的意义在于它以抽象方法的形式缺少功能。如果一个类没有任何抽象行为(在不同类型之间变化),那么它可能是一个具体的类。

抽象类是类

下面是类的一些常规特性,这些特性在抽象类中是可用的,但在接口中是不可用的:

实例变量/非最终变量。因此…… 可以访问和修改对象状态的方法 私有/受保护成员(但请参阅Java 9的注释) 扩展抽象或具体类的能力 构造函数

关于抽象类需要注意的几点:

它们不可能是最终的(因为它们的全部目的是扩展) 扩展另一个抽象类的抽象类继承其所有抽象方法作为自己的抽象方法

抽象方法

抽象类和接口都可以有0到多个抽象方法。抽象方法:

是没有主体的方法签名(即没有{}) 在抽象类中必须用abstract关键字标记。在接口中,该关键字是不必要的 不能是私有的(因为它们需要由另一个类实现) 不能最终确定(因为他们还没有身体) 不能是静态的(因为原因)

还要注意:

抽象方法可以由同一类/接口中的非抽象方法调用 扩展抽象类或实现接口的第一个具体类必须为所有抽象方法提供实现

静态方法

抽象类上的静态方法可以直接使用MyAbstractClass.method()调用;(例如,就像一个普通的类,它也可以通过一个扩展抽象类的类来调用)。

接口也可以有静态方法。它们只能通过接口的名称来调用(MyInterface.method();)。这些方法:

不能是抽象的,即必须有一个主体(参见上面的“因为原因”) 不是默认值(见下文)

默认的方法

接口可以有默认方法,该方法必须有default关键字和方法体。这些方法只能引用其他接口方法(不能引用特定实现的状态)。这些方法:

不是静止的 不是抽象的(他们有一个主体) 不能为最终值(名称“default”表示它们可能被覆盖)

如果一个类实现了两个具有相同签名的缺省方法的接口,则会导致编译错误,这可以通过覆盖该方法来解决。

接口可以有静态的final常量

接口只能包含上面描述的方法类型或常量。

常量被假定为静态的和最终的,并且可以在实现接口的类中不加限制地使用。

所有接口成员都是公共的

在Java 8中,接口的所有成员(以及接口本身)都被假定为公共的,不能被保护或私有(但Java 9确实允许接口中的私有方法)。

这意味着实现接口的类必须定义具有公共可见性的方法(与常规规则一致,方法不能被低可见性覆盖)。

你很好地总结了使用和实现方面的实际差异,但没有提到意义上的差异。

接口是实现类将具有的行为的描述。实现类确保它将拥有这些可以在其上使用的方法。它基本上是类必须做出的契约或承诺。

抽象类是不同子类的基础,这些子类共享不需要重复创建的行为。子类必须完成行为,并有覆盖预定义行为的选项(只要它没有被定义为final或private)。

你会在java中找到很好的例子。util包,它包含了List这样的接口和AbstractList这样已经实现了接口的抽象类。官方文档对AbstractList的描述如下:

该类提供了List接口的框架实现,以最大限度地减少实现该接口所需的工作,该接口由“随机访问”数据存储(例如数组)支持。

这里似乎已经涵盖了几乎所有的东西。在抽象类的实际实现上再补充一点:

Abstract关键字也用于防止类被实例化。如果你有一个具体的类,你不想被实例化-让它抽象。

我所观察到的主要区别是抽象类为我们提供了一些已经实现的常见行为,而子类只需要实现与它们对应的特定功能。而接口只指定需要完成哪些任务,接口不会给出任何实现。我可以说它指定了它自己和实现的类之间的契约。