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

以下是我的回答:

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.

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

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

我哪里错了?


当前回答

1.1抽象类与接口的区别

1.1.1. Abstract classes versus interfaces in Java 8
1.1.2. Conceptual Difference:

1.2 Java 8中的接口默认方法

1.2.1. What is Default Method?
1.2.2. ForEach method compilation error solved using Default Method
1.2.3. Default Method and Multiple Inheritance Ambiguity Problems
1.2.4. Important points about java interface default methods:

1.3 Java接口静态方法

1.3.1. Java Interface Static Method, code example, static method vs default method
1.3.2. Important points about java interface static method:

1.4 Java功能接口



1.1.1. Java 8中的抽象类与接口

Java 8 interface changes include static methods and default methods in interfaces. Prior to Java 8, we could have only method declarations in the interfaces. But from Java 8, we can have default methods and static methods in the interfaces. After introducing Default Method, it seems that interfaces and abstract classes are same. However, they are still different concept in Java 8. Abstract class can define constructor. They are more structured and can have a state associated with them. While in contrast, default method can be implemented only in the terms of invoking other interface methods, with no reference to a particular implementation's state. Hence, both use for different purposes and choosing between two really depends on the scenario context.

1.1.2. 概念上的差别:

抽象类对接口的骨架(即部分)实现有效,但如果没有匹配的接口就不应该存在。

因此,当抽象类被有效地简化为低可见性的接口骨架实现时,默认方法是否也能消除这种情况呢?毫无疑问:不!实现接口几乎总是需要一些或全部的类构建工具,而默认方法不具备这些工具。如果某些接口没有,这显然是一个特例,不应该让您误入歧途。

1.2 Java 8中的接口默认方法

Java 8引入了“默认方法”或(防御方法)新特性,允许开发人员在不破坏现有接口实现的情况下向接口添加新方法。它提供了灵活性,允许接口定义实现,在具体类无法为该方法提供实现的情况下,接口定义实现将作为默认值使用。

让我们看一个小例子来理解它是如何工作的:

public interface OldInterface {
    public void existingMethod();
 
    default public void newDefaultMethod() {
        System.out.println("New default method"
               + " is added in interface");
    }
}

下面的类将在Java JDK 8中成功编译,

public class OldInterfaceImpl implements OldInterface {
    public void existingMethod() {
     // existing implementation is here…
    }
}

如果你创建一个OldInterfaceImpl实例:

OldInterfaceImpl obj = new OldInterfaceImpl ();
// print “New default method add in interface”
obj.newDefaultMethod(); 

1.2.1. 默认的方法:

默认方法从来不是最终的,不能同步,也不能 重写Object的方法。他们总是公开的,这很严重 限制了编写简短且可重用方法的能力。

缺省方法可以提供给接口而不影响类的实现,因为它包含实现。如果在用实现定义的接口中添加的每个方法都不影响实现类。实现类可以覆盖接口提供的默认实现。

默认方法允许向现有接口添加新功能 而不会破坏这些接口的旧实现。

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

不重写默认方法,将继承默认方法。 重写默认方法,类似于重写中的其他方法 子类。 将默认方法重新声明为抽象,从而强制子类为 覆盖它。


1.2.2. 使用默认方法解决了所有方法编译错误

对于Java 8, JDK集合已经被扩展,forEach方法被添加到整个集合中(它与lambdas一起工作)。使用常规方法,代码如下所示,

public interface Iterable<T> {
    public void forEach(Consumer<? super T> consumer);
}

由于此结果每个实现类都有编译错误,因此,默认方法添加了必需的实现,以便现有的实现不应被更改。

具有默认方法的可迭代接口如下所示,

public interface Iterable<T> {
    public default void forEach(Consumer
                   <? super T> consumer) {
        for (T t : this) {
            consumer.accept(t);
        }
    }
}

同样的机制已经被用于在不破坏实现类的情况下在JDK接口中添加流。


1.2.3. 默认方法与多重继承歧义问题

由于java类可以实现多个接口,并且每个接口可以定义具有相同方法签名的默认方法,因此,继承的方法之间可能会发生冲突。

考虑下面的例子,

public interface InterfaceA {  
       default void defaultMethod(){  
           System.out.println("Interface A default method");  
    }  
}
 
public interface InterfaceB {
   default void defaultMethod(){
       System.out.println("Interface B default method");
   }
}
 
public class Impl implements InterfaceA, InterfaceB  {
}

上述代码将无法编译,并出现以下错误:

类Impl继承defaultMethod()的不相关默认值 类型为InterfaceA和InterfaceB

为了修复这个类,我们需要提供默认的方法实现:

public class Impl implements InterfaceA, InterfaceB {
    public void defaultMethod(){
    }
}

此外,如果我们想调用任何一个super Interface提供的默认实现,而不是我们自己的实现,我们可以这样做:

public class Impl implements InterfaceA, InterfaceB {
    public void defaultMethod(){
        // existing code here..
        InterfaceA.super.defaultMethod();
    }
}

我们可以选择任何默认实现,也可以同时选择两者作为新方法的一部分。

1.2.4. java接口默认方法的要点:

Java interface default methods will help us in extending interfaces without having the fear of breaking implementation classes. Java interface default methods have bridge down the differences between interfaces and abstract classes. Java 8 interface default methods will help us in avoiding utility classes, such as all the Collections class method can be provided in the interfaces itself. Java interface default methods will help us in removing base implementation classes, we can provide default implementation and the implementation classes can chose which one to override. One of the major reason for introducing default methods in interfaces is to enhance the Collections API in Java 8 to support lambda expressions. If any class in the hierarchy has a method with same signature, then default methods become irrelevant. A default method cannot override a method from java.lang.Object. The reasoning is very simple, it’s because Object is the base class for all the java classes. So even if we have Object class methods defined as default methods in interfaces, it will be useless because Object class method will always be used. That’s why to avoid confusion, we can’t have default methods that are overriding Object class methods. Java interface default methods are also referred to as Defender Methods or Virtual extension methods.

资源链接:

何时使用:Java 8+接口默认方法,vs.抽象方法 JDK 8时代的抽象类与接口 通过虚拟扩展方法进行接口演化


1.3 Java接口静态方法

1.3.1. Java接口静态方法,代码示例,静态方法与默认方法

Java接口静态方法与默认方法类似,只是我们不能在实现类中重写它们。这个特性可以帮助我们避免在实现类中实现不好的情况下产生不希望看到的结果。让我们用一个简单的例子来研究这个问题。

public interface MyData {

    default void print(String str) {
        if (!isNull(str))
            System.out.println("MyData Print::" + str);
    }

    static boolean isNull(String str) {
        System.out.println("Interface Null Check");

        return str == null ? true : "".equals(str) ? true : false;
    }
}

现在让我们来看看一个实现类,它有一个isNull()方法,但实现很差。

public class MyDataImpl implements MyData {

    public boolean isNull(String str) {
        System.out.println("Impl Null Check");

        return str == null ? true : false;
    }
    
    public static void main(String args[]){
        MyDataImpl obj = new MyDataImpl();
        obj.print("");
        obj.isNull("abc");
    }
}

注意isNull(String str)是一个简单的类方法,它不会覆盖接口方法。例如,如果我们将@Override注释添加到isNull()方法,它将导致编译器错误。

现在,当我们运行应用程序时,会得到以下输出。

接口空检查 Impl Null检查

如果我们将接口方法从静态改为默认,我们将得到以下输出。

Impl Null检查 MyData打印: Impl Null检查

Java接口静态方法只对接口方法可见,如果我们从MyDataImpl类中删除isNull()方法,我们将不能将它用于MyDataImpl对象。然而,像其他静态方法一样,我们可以使用类名来使用接口静态方法。例如,一个有效的语句将是:

boolean result = MyData.isNull("abc");

1.3.2. java接口静态方法的要点:

Java interface static method is part of interface, we can’t use it for implementation class objects. Java interface static methods are good for providing utility methods, for example null check, collection sorting etc. Java interface static method helps us in providing security by not allowing implementation classes to override them. We can’t define interface static method for Object class methods, we will get compiler error as “This static method cannot hide the instance method from Object”. This is because it’s not allowed in java, since Object is the base class for all the classes and we can’t have one class level static method and another instance method with same signature. We can use java interface static methods to remove utility classes such as Collections and move all of it’s static methods to the corresponding interface, that would be easy to find and use.


1.4 Java功能接口

在我结束这篇文章之前,我想简单介绍一下Functional接口。只有一个抽象方法的接口称为功能接口。

引入了一个新的注释@FunctionalInterface,将接口标记为Functional interface。@FunctionalInterface注释是避免在函数接口中意外添加抽象方法的工具。这是可选的,但使用它是很好的实践。

函数式接口是人们期待已久的Java 8特性,因为它允许我们使用lambda表达式来实例化它们。添加了一个新的包java.util.function,其中包含一系列函数接口,为lambda表达式和方法引用提供目标类型。我们将在以后的文章中讨论函数接口和lambda表达式。

资源位置:

Java 8接口更改-静态方法,默认方法

其他回答

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

"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."

祝你下次面试好运!

抽象类不是纯粹的抽象,因为它是具体方法(已实现方法)和未实现方法的集合。 但 接口是纯抽象的,因为只有未实现的方法,没有具体的方法。

为什么是抽象类?

如果用户想为所有对象编写通用功能。 抽象类是未来重新实现的最佳选择,可以在不影响最终用户的情况下增加更多的功能。

为什么接口?

如果用户想要编写不同的功能,那将是对象上的不同功能。 一旦接口发布,如果不需要修改需求,接口是最好的选择。

接口是纯粹抽象的。我们在接口中没有任何实现代码。

抽象类包含方法及其实现。

点击这里观看接口和抽象类教程

你的解释看起来还不错,但可能看起来像是你从课本上读的?: - /

我更关心的是,你的例子有多可靠?你是否费心去包括抽象和接口之间几乎所有的区别?

就我个人而言,我建议这个链接: http://mindprod.com/jgloss/interfacevsabstract.html#TABLE

对于差异的详尽列表..

希望它能帮助你和所有其他读者在未来的采访

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

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

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

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