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

以下是我的回答:

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.

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

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

我哪里错了?


当前回答

你的所有语句都是有效的,除了你的第一个语句(在Java 8发布之后):

Java接口的方法是隐式抽象的,不能有实现

从文档页:

接口是一种引用类型,类似于类,只能包含 常量、方法签名、默认方法、静态方法和嵌套类型 方法体只存在于默认方法和静态方法中。

默认的方法:

接口可以有默认方法,但与抽象类中的抽象方法不同。

默认方法使您能够向库的接口添加新功能,并确保与为这些接口的旧版本编写的代码的二进制兼容性。

当您扩展一个包含默认方法的接口时,您可以执行以下操作:

完全不要提及默认方法,这将使您的扩展接口继承默认方法。 重新声明默认方法,使其抽象。 重新定义默认方法,该方法将覆盖它。

静态方法:

除了默认方法外,还可以在接口中定义静态方法。静态方法是与定义它的类相关联的方法,而不是与任何对象相关联的方法。类的每个实例都共享它的静态方法。)

这让你更容易在你的库中组织帮助方法;

来自文档页的关于接口具有静态和默认方法的示例代码。

import java.time.*;

public interface TimeClient {
    void setTime(int hour, int minute, int second);
    void setDate(int day, int month, int year);
    void setDateAndTime(int day, int month, int year,
                               int hour, int minute, int second);
    LocalDateTime getLocalDateTime();

    static ZoneId getZoneId (String zoneString) {
        try {
            return ZoneId.of(zoneString);
        } catch (DateTimeException e) {
            System.err.println("Invalid time zone: " + zoneString +
                "; using default time zone instead.");
            return ZoneId.systemDefault();
        }
    }

    default ZonedDateTime getZonedDateTime(String zoneString) {
        return ZonedDateTime.of(getLocalDateTime(), getZoneId(zoneString));
    }
}

使用下面的指导原则来选择是使用接口还是抽象类。

接口:

定义一个契约(最好是无状态的——我的意思是没有变量) 将不相关的类链接到具有一种功能。 声明公共常量变量(不可变状态)

抽象类:

在几个密切相关的类之间共享代码。它建立了一种关系。 在相关类之间共享公共状态(状态可以在具体类中修改)

相关文章:

接口与抽象类(通用面向对象)

实现和扩展:什么时候使用?有什么不同?

通过这些例子,你可以理解

不相关的类可以通过接口具有功能,但相关类通过扩展基类来改变行为。

其他回答

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

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

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

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

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

下面是一个围绕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确实允许接口中的私有方法)。

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

这个世界上没有什么是完美的。他们可能期待的是更实际的方法。

但在你解释之后,你可以用稍微不同的方法添加这些行。

Interfaces are rules (rules because you must give an implementation to them that you can't ignore or avoid, so that they are imposed like rules) which works as a common understanding document among various teams in software development. Interfaces give the idea what is to be done but not how it will be done. So implementation completely depends on developer by following the given rules (means given signature of methods). Abstract classes may contain abstract declarations, concrete implementations, or both. Abstract declarations are like rules to be followed and concrete implementations are like guidelines (you can use it as it is or you can ignore it by overriding and giving your own implementation to it). Moreover which methods with same signature may change the behaviour in different context are provided as interface declarations as rules to implement accordingly in different contexts.

编辑:Java 8简化了在接口中定义默认方法和静态方法。

public interface SomeInterfaceOne {

    void usualAbstractMethod(String inputString);

    default void defaultMethod(String inputString){
        System.out.println("Inside SomeInterfaceOne defaultMethod::"+inputString);
    }
}

现在,当一个类将实现SomeInterface时,并不强制为interface的默认方法提供实现。

如果我们有另一个具有以下方法的接口:

public interface SomeInterfaceTwo {

    void usualAbstractMethod(String inputString);

    default void defaultMethod(String inputString){
        System.out.println("Inside SomeInterfaceTwo defaultMethod::"+inputString);
    }

}

Java不允许扩展多个类,因为这会导致“钻石问题”,即编译器无法决定使用哪个超类方法。如果使用默认方法,接口也会出现菱形问题。因为如果一个类同时实现这两个

SomeInterfaceOne and SomeInterfaceTwo

并且没有实现常见的默认方法,编译器不能决定选择哪一个。 为了避免这个问题,在java 8中必须实现不同接口的通用缺省方法。如果任何类实现了上述两个接口,它必须提供defaultMethod()方法的实现,否则编译器将抛出编译时错误。

你的所有语句都是有效的,除了你的第一个语句(在Java 8发布之后):

Java接口的方法是隐式抽象的,不能有实现

从文档页:

接口是一种引用类型,类似于类,只能包含 常量、方法签名、默认方法、静态方法和嵌套类型 方法体只存在于默认方法和静态方法中。

默认的方法:

接口可以有默认方法,但与抽象类中的抽象方法不同。

默认方法使您能够向库的接口添加新功能,并确保与为这些接口的旧版本编写的代码的二进制兼容性。

当您扩展一个包含默认方法的接口时,您可以执行以下操作:

完全不要提及默认方法,这将使您的扩展接口继承默认方法。 重新声明默认方法,使其抽象。 重新定义默认方法,该方法将覆盖它。

静态方法:

除了默认方法外,还可以在接口中定义静态方法。静态方法是与定义它的类相关联的方法,而不是与任何对象相关联的方法。类的每个实例都共享它的静态方法。)

这让你更容易在你的库中组织帮助方法;

来自文档页的关于接口具有静态和默认方法的示例代码。

import java.time.*;

public interface TimeClient {
    void setTime(int hour, int minute, int second);
    void setDate(int day, int month, int year);
    void setDateAndTime(int day, int month, int year,
                               int hour, int minute, int second);
    LocalDateTime getLocalDateTime();

    static ZoneId getZoneId (String zoneString) {
        try {
            return ZoneId.of(zoneString);
        } catch (DateTimeException e) {
            System.err.println("Invalid time zone: " + zoneString +
                "; using default time zone instead.");
            return ZoneId.systemDefault();
        }
    }

    default ZonedDateTime getZonedDateTime(String zoneString) {
        return ZonedDateTime.of(getLocalDateTime(), getZoneId(zoneString));
    }
}

使用下面的指导原则来选择是使用接口还是抽象类。

接口:

定义一个契约(最好是无状态的——我的意思是没有变量) 将不相关的类链接到具有一种功能。 声明公共常量变量(不可变状态)

抽象类:

在几个密切相关的类之间共享代码。它建立了一种关系。 在相关类之间共享公共状态(状态可以在具体类中修改)

相关文章:

接口与抽象类(通用面向对象)

实现和扩展:什么时候使用?有什么不同?

通过这些例子,你可以理解

不相关的类可以通过接口具有功能,但相关类通过扩展基类来改变行为。

我为工作做面试,我也会不看好你的答案(抱歉,但我很诚实)。听起来你确实读过不同之处并修改了答案,但也许你从未在实践中使用过。

一个好的解释为什么你会使用每一种,比一个精确的解释差异要好得多。雇主最终希望程序员做一些他们不了解的事情,而这些事情很难在面试中展示出来。如果你申请的是技术或文档相关的工作,而不是开发人员的职位,你给出的答案会很好。

祝你以后面试顺利。

另外,我对这个问题的回答更多的是关于面试技巧,而不是你提供的技术材料。或许可以考虑读一读。https://workplace.stackexchange.com/是做这类事情的好地方。