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

以下是我的回答:

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.

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

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

我哪里错了?


当前回答

当我试图在两个密切相关的类之间共享行为时,我创建了一个包含公共行为的抽象类,并作为两个类的父类。

当我试图定义Type(对象的用户可以可靠地调用的方法列表)时,我创建了一个接口。

例如,我绝不会创建一个只有一个具体子类的抽象类,因为抽象类是关于共享行为的。但是我很可能创建一个只有一个实现的接口。我的代码的用户不会知道只有一个实现。实际上,在未来的版本中可能会有几个实现,它们都是一些新的抽象类的子类,这些抽象类在我创建接口时甚至还不存在。

这似乎也有点太书生气了(尽管我在记忆中从未见过这样的说法)。如果面试官(或OP)真的想要更多关于这方面的个人经验,我早就准备好了关于界面是出于必要而进化的轶事,反之亦然。

One more thing. Java 8 now allows you to put default code into an interface, further blurring the line between interfaces and abstract classes. But from what I have seen, that feature is overused even by the makers of the Java core libraries. That feature was added, and rightly so, to make it possible to extend an interface without creating binary incompatibility. But if you are making a brand new Type by defining an interface, then the interface should be JUST an interface. If you want to also provide common code, then by all means make a helper class (abstract or concrete). Don't be cluttering your interface from the start with functionality that you may want to change.

其他回答

简而言之,我想这样回答:

通过类层次结构进行继承意味着状态继承; 而通过接口继承则代表行为继承;

抽象类可以被视为介于这两种情况之间的东西(它引入了一些状态,但也迫使你定义一个行为),完全抽象类是一个接口(据我所知,这是c++中仅由虚拟方法组成的类的进一步发展)。

当然,从Java 8开始,事情发生了轻微的变化,但思想仍然是一样的。

我想这对于一个典型的Java面试来说已经足够了,如果你不是被编译器团队面试的话。

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

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

你的回答是对的,但是面试官需要你从软件工程的角度来区分,而不是根据Java的细节。

简单的单词:

An Interface is like the interface of a shop anything that is shown on it should be there in the shop, so any method in the Interface must be there implemented in the concrete class. Now what if some classes share some exact methods and varies in others. Suppose the Interface is about a shop that contains two things and suppose we have two shops both contain sport equipment but one has clothes extra and the other has shoes extra. So what you do is making an abstract class for Sport that implements the Sports method and leave the other method unimplemented. Abstract class here means that this shop doesn't exist itself but it is the base for other classes/shops. This way you are organising the code, avoiding errors of replicating the code, unifying the code, and ensuring re-usability by some other class.

当我试图在两个密切相关的类之间共享行为时,我创建了一个包含公共行为的抽象类,并作为两个类的父类。

当我试图定义Type(对象的用户可以可靠地调用的方法列表)时,我创建了一个接口。

例如,我绝不会创建一个只有一个具体子类的抽象类,因为抽象类是关于共享行为的。但是我很可能创建一个只有一个实现的接口。我的代码的用户不会知道只有一个实现。实际上,在未来的版本中可能会有几个实现,它们都是一些新的抽象类的子类,这些抽象类在我创建接口时甚至还不存在。

这似乎也有点太书生气了(尽管我在记忆中从未见过这样的说法)。如果面试官(或OP)真的想要更多关于这方面的个人经验,我早就准备好了关于界面是出于必要而进化的轶事,反之亦然。

One more thing. Java 8 now allows you to put default code into an interface, further blurring the line between interfaces and abstract classes. But from what I have seen, that feature is overused even by the makers of the Java core libraries. That feature was added, and rightly so, to make it possible to extend an interface without creating binary incompatibility. But if you are making a brand new Type by defining an interface, then the interface should be JUST an interface. If you want to also provide common code, then by all means make a helper class (abstract or concrete). Don't be cluttering your interface from the start with functionality that you may want to change.

接口由单例变量(公共静态final)和公共抽象方法组成。当我们知道要做什么但不知道如何做时,我们通常更喜欢实时使用接口。

这个概念可以通过以下例子更好地理解:

考虑一个支付类。支付方式有很多种,比如PayPal,信用卡等。因此,我们通常将Payment作为接口,其中包含makePayment()方法,CreditCard和PayPal是两个实现类。

public interface Payment
{
    void makePayment();//by default it is a abstract method
}
public class PayPal implements Payment
{
    public void makePayment()
    {
        //some logic for PayPal payment
        //e.g. Paypal uses username and password for payment
    }
}
public class CreditCard implements Payment
{
    public void makePayment()
    {
        //some logic for CreditCard payment
        //e.g. CreditCard uses card number, date of expiry etc...
    }
}

在上面的例子中,CreditCard和PayPal是两个实现类/策略。接口还允许我们在Java中实现多重继承的概念,这是抽象类无法实现的。

当我们知道某些特性该做什么,而其他特性又知道如何执行时,我们就会选择一个抽象类。

考虑下面的例子:

public abstract class Burger
{
    public void packing()
    {
        //some logic for packing a burger
    }
    public abstract void price(); //price is different for different categories of burgers
}
public class VegBerger extends Burger
{
    public void price()
    {
        //set price for a veg burger.
    }
}
public class NonVegBerger extends Burger
{
    public void price()
    {
        //set price for a non-veg burger.
    }
}

如果我们将来在给定的抽象类中添加方法(具体的/抽象的),那么实现类将不需要更改其代码。但是,如果将来在接口中添加方法,则必须将实现添加到实现该接口的所有类中,否则会发生编译时错误。

还有其他不同之处,但这些都是面试官所期望的。希望这对你们有帮助。