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

以下是我的回答:

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.

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

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

我哪里错了?


当前回答

我先给大家举个例子:

public interface LoginAuth{
   public String encryptPassword(String pass);
   public void checkDBforUser();
}

假设您的应用程序中有3个数据库。然后,该数据库的每个实现都需要定义上述2个方法:

public class DBMySQL implements LoginAuth{
          // Needs to implement both methods
}
public class DBOracle implements LoginAuth{
          // Needs to implement both methods
}
public class DBAbc implements LoginAuth{
          // Needs to implement both methods
}

但是,如果encryptPassword()不依赖于数据库,并且对每个类都是相同的呢?那么上面的方法就不是一个好的方法。

相反,考虑以下方法:

public abstract class LoginAuth{
   public String encryptPassword(String pass){
            // Implement the same default behavior here 
            // that is shared by all subclasses.
   }

   // Each subclass needs to provide their own implementation of this only:
   public abstract void checkDBforUser();
}

现在,在每个子类中,我们只需要实现一个方法——依赖于数据库的方法。

其他回答

是的,从技术上讲,你的回答是正确的,但你的错误之处在于,你没有向他们表明你理解选择其中一个的利弊。此外,他们可能担心将来升级时代码库的兼容性问题。这种类型的回答可能有帮助(除了你说的):

"Choosing an Abstract Class over an Interface Class depends on what we project the future of the code will be. Abstract classes allow better forward-compatibility because you can continue adding behavior to an Abstract Class well into the future without breaking your existing code --> this is not possible with an Interface Class. On the other hand, Interface Classes are more flexible than Abstract Classes. This is because they can implement multiple interfaces. The thing is Java does not have multiple inheritances so using abstract classes won't let you use any other class hierarchy structure... So, in the end a good general rule of thumb is: Prefer using Interface Classes when there are no existing/default implementations in your codebase. And, use Abstract Classes to preserve compatibility if you know you will be updating your class in the future."

祝你下次面试好运!

就连我也在多次面试中遇到过同样的问题,相信我,说服面试官会让你很痛苦。 如果我固有以上所有的答案,那么我需要增加一个关键点,使它更有说服力,并充分利用OO

如果你不打算在规则中进行任何修改,对于子类来说,在很长一段时间内,去接口,因为你不能在其中修改,如果你这样做,你需要在所有其他子类中进行更改,然而,如果你认为,你想重用功能,设置一些规则并使其开放修改,去抽象类。

想象一下,你使用了一个可消费的服务,或者你向世界提供了一些代码,你有机会修改一些东西,假设是一个安全检查 如果我是代码的消费者,并且在更新后的某个早上,我发现Eclipse中所有的读标记,整个应用程序都关闭了。 因此,为了避免这样的噩梦,请在接口上使用抽象

我想这会在一定程度上说服面试官……愉快的面试。

Many junior developers make the mistake of thinking of interfaces, abstract and concrete classes as slight variations of the same thing, and choose one of them purely on technical grounds: Do I need multiple inheritance? Do I need some place to put common methods? Do I need to bother with something other than just a concrete class? This is wrong, and hidden in these questions is the main problem: "I". When you write code for yourself, by yourself, you rarely think of other present or future developers working on or with your code.

接口和抽象类,虽然从技术的角度来看很相似,但它们的含义和目的完全不同。

总结

接口定义了一个契约,由某个实现为您实现。 抽象类提供了您的实现可以重用的默认行为。

以上两点正是我在面试时所寻求的,并且是一个足够紧凑的总结。阅读更多细节。

替代的总结

接口用于定义公共api 抽象类用于内部使用和定义spi

通过例子

To put it differently: A concrete class does the actual work, in a very specific way. For example, an ArrayList uses a contiguous area of memory to store a list of objects in a compact manner which offers fast random access, iteration, and in-place changes, but is terrible at insertions, deletions, and occasionally even additions; meanwhile, a LinkedList uses double-linked nodes to store a list of objects, which instead offers fast iteration, in-place changes, and insertion/deletion/addition, but is terrible at random access. These two types of lists are optimized for different use cases, and it matters a lot how you're going to use them. When you're trying to squeeze performance out of a list that you're heavily interacting with, and when picking the type of list is up to you, you should carefully pick which one you're instantiating.

On the other hand, high level users of a list don't really care how it is actually implemented, and they should be insulated from these details. Let's imagine that Java didn't expose the List interface, but only had a concrete List class that's actually what LinkedList is right now. All Java developers would have tailored their code to fit the implementation details: avoid random access, add a cache to speed up access, or just reimplement ArrayList on their own, although it would be incompatible with all the other code that actually works with List only. That would be terrible... But now imagine that the Java masters actually realize that a linked list is terrible for most actual use cases, and decided to switch over to an array list for their only List class available. This would affect the performance of every Java program in the world, and people wouldn't be happy about it. And the main culprit is that implementation details were available, and the developers assumed that those details are a permanent contract that they can rely on. This is why it's important to hide implementation details, and only define an abstract contract. This is the purpose of an interface: define what kind of input a method accepts, and what kind of output is expected, without exposing all the guts that would tempt programmers to tweak their code to fit the internal details that might change with any future update.

抽象类介于接口和具体类之间。它应该帮助实现共享常见或无聊的代码。例如,AbstractCollection提供了基于大小为0的isEmpty的基本实现,contains作为迭代和比较,addAll作为重复添加,等等。这使得实现将重点放在区分它们的关键部分:如何实际存储和检索数据。

另一个角度:api与spi

接口是代码不同部分之间的低内聚网关。它们允许库的存在和发展,而不会在内部发生变化时影响到每个库的用户。它被称为应用程序编程接口,而不是应用程序编程类。在较小的规模上,它们还允许多个开发人员在大型项目上成功协作,通过良好的文档接口分离不同的模块。

抽象类是在实现接口时使用的高内聚帮助器,假设有某种级别的实现细节。或者,抽象类用于定义服务提供者接口(spi)。

API和SPI之间的区别很微妙,但很重要:对于API,重点在于谁使用它,而对于SPI,重点在于谁实现它。

Adding methods to an API is easy, all existing users of the API will still compile. Adding methods to an SPI is hard, since every service provider (concrete implementation) will have to implement the new methods. If interfaces are used to define an SPI, a provider will have to release a new version whenever the SPI contract changes. If abstract classes are used instead, new methods could either be defined in terms of existing abstract methods, or as empty throw not implemented exception stubs, which will at least allow an older version of a service implementation to still compile and run.

关于Java 8和默认方法的说明

尽管Java 8为接口引入了默认方法,这使得接口和抽象类之间的界限更加模糊,但这并不是为了实现可以重用代码,而是为了更容易地更改既作为API又作为SPI(或者被错误地用于定义SPI而不是抽象类)的接口。

“书本知识”

OP回答中提供的技术细节被认为是“书本知识”,因为这通常是在学校和大多数关于语言的技术书籍中使用的方法:一个东西是什么,而不是如何在实践中使用它,特别是在大规模应用中。

打个比方:假设问题是:

舞会之夜租什么更好,一辆车还是一间酒店房间?

技术上的答案是这样的:

嗯,在车里你可以做得更快,但在酒店房间里你可以做得更舒服。另一方面,酒店房间只在一个地方,而在汽车里你可以在更多的地方这样做,比如,你可以去远景点看风景,或者在汽车电影院,或者很多其他地方,甚至不止一个地方。而且,酒店房间里有淋浴。

这都是真的,但完全忽略了一点,那就是它们是两种完全不同的东西,两者都可以同时用于不同的目的,“做”方面并不是这两种选择中最重要的事情。这个答案缺乏视角,它显示了一种不成熟的思维方式,而正确地呈现了真实的“事实”。

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

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

例如,说我有一个执行一些操作的任务类,现在在单独的线程中执行一个任务,我不需要扩展线程类,更好的选择是使任务实现可运行的接口(即实现其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接口和抽象类甚至更窄。

接口由单例变量(公共静态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.
    }
}

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

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