ChatGPT解决这个技术问题 Extra ChatGPT

我应该如何解释接口和抽象类之间的区别?

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

这是我的回应:

Java 接口的方法是隐式抽象的,不能有实现。 Java 抽象类可以具有实现默认行为的实例方法。在 Java 接口中声明的变量默认是 final 的。抽象类可能包含非最终变量。默认情况下,Java 接口的成员是公共的。 Java 抽象类可以具有通常的类成员风格,例如私有、受保护等。Java 接口应该使用关键字“implements”来实现; Java 抽象类应该使用关键字“extends”进行扩展。一个接口只能扩展另一个Java接口,一个抽象类可以扩展另一个Java类并实现多个Java接口。一个 Java 类可以实现多个接口,但它只能扩展一个抽象类。

但是,面试官并不满意,告诉我这个描述代表了“书本知识”。

他要求我给出更实际的回应,并使用实际示例解释我何时会选择抽象类而不是接口。

我哪里做错了?

也许你的回答看起来像是在说一些你不明白的事情?可能您只需要将讲述方式更改为更像您自己的话的方式。
您回答了一份(非常正确的)技术差异列表。面试官很可能正在寻找一个更具概念性的答案(例如,在使用接口和抽象类之间选择的基础是什么)。
你忘了说抽象类有构造函数,即使你不能实例化一个抽象类,const。由子类使用。接口指示“什么”而不是“如何”,因为它们定义了一个合同(方法列表),而一个abst。类还可以指示“如何”(实现方法)。使用整数。您可以模拟多重继承(一个类可以实现多个int。但只能扩展一个类)。使用整数。你可以有一个基本类型的差异。家庭:传单 f=new Plane();传单 f2=new Bird(); Bird和Plane不对应同一个家族,但都可以飞行(是传单)。
由于 java8 接口可以包含方法..所以超越 OO 概念这些所谓的“差异”可以随时改变。
我对你的回答没有任何问题,我认为面试官没有任何业务可以嘲笑“书本知识”。面试官并不总是知道他们所问问题的正确答案,有些面试只是警告你不要在那里工作。

C
CopsOnRoad

我先给你举个例子:

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

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

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();
}

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


我不确定这是否真的解释了差异......当然这是一个很好的技术。我想还值得指出的是,Java 8 终于承认 C++ 是正确的,并且可以完成多重继承并且可以使用,因此接口现在不仅可以定义函数签名,还可以提供默认实现。因此,最好使用接口。
@thecoshman如果我按照答案(实现一个方法的抽象类和另一个抽象的抽象类)或定义一个具有默认方法实现的接口来解决问题,会有什么不同?基本上,我想说的是您写道“使用界面会更好”,而我的问题是 - 为什么?
所以,我想公平地说,对于接口,定义的实现取决于实际实现接口的类,而抽象类中的东西对于扩展类的类来说是“核心”;即,它不会改变。
@Neutrino 尽管Java允许您实现多个接口,每个接口都提供函数的默认实现,但您仍然只能扩展一个类。因此,使用接口可以为想要使用它的人以及其他接口提供更大的灵活性。
@HiradNikoo 很抱歉评论晚了,但我偶然发现了这个帖子。您也可以将类继承视为 IS-A 关系,而接口表示“具有某种功能”。
S
Shailesh Saxena

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

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

接口是规则(规则,因为您必须为它们提供一个您不能忽略或避免的实现,因此它们就像规则一样被强加),它作为软件开发中各个团队之间的共同理解文档。接口给出了要做什么而不是如何完成的想法。所以实现完全取决于开发人员遵循给定的规则(意味着给定的方法签名)。抽象类可能包含抽象声明、具体实现或两者兼有。抽象声明就像要遵循的规则,而具体的实现就像指导方针(您可以按原样使用它,也可以通过覆盖并给它自己的实现来忽略它)。此外,哪些具有相同签名的方法可能会改变不同上下文中的行为作为接口声明提供,作为在不同上下文中相应实现的规则。

编辑:Java 8 有助于在接口中定义默认和静态方法。

public interface SomeInterfaceOne {

    void usualAbstractMethod(String inputString);

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

现在当一个类实现 SomeInterface 时,不必为接口的默认方法提供实现。

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

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() 方法的实现,否则编译器将抛出编译时错误。


+1,这确实是避免混淆的好答案。但我没有看到任何链接,也不知道你为什么引用那些有价值的行。如果可能的话,把它们作为积分:)。
阅读我上面关于使用接口模拟多重继承和使用接口为不同系列的类提供基类型的评论。我认为面试官希望从被采访者那里听到这样的答案。
您的评论还指出了一个很好的接口使用示例。我写了我每天工作时的感受。这些话可能不专业或不准确。但这是我在日常编码中与抽象类和接口密切合作后才知道的。
4. 具体实现也是规则,有默认实现。
@Luten:据我所知,如果您可以毫无问题地避免/忽略规则,那一定是准则而不是规则。如果我错了,请纠正我。
A
Abhishek Jain

您很好地总结了使用和实现的实际差异,但没有说明含义上的差异。

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

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

您会在 java.util 包中找到很好的示例,其中包括像 List 这样的接口和已经实现接口的像 AbstractList 这样的抽象类。 official documentationAbstractList 的描述如下:

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


这应该是答案。不是详细的列表,而是在接口和抽象类之间产生差异的底层概念,不仅在 Java 中,而且在一般情况下。
这真的很好。当然其他答案也很好。但这告诉您一个关于 abstract 关键字的主要提示,即当编译器看到这个时,他们知道,以下信息是不完整并且需要实现。接口总是不完整的,但抽象类是抽象的,因为它们必须有 incomplete (abstract) 方法。
R
Ravindra babu

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

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

考虑一个支付类。可以通过多种方式进行支付,例如 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.
    }
}

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

还有其他差异,但这些是主要的差异,可能是您的面试官所期望的。希望这会有所帮助。


嗯,这个答案很有意义,而且这个例子很清楚,当我们在 interfaceabstract class 之间进行选择时。
“做什么,但不知道怎么做”,因为我们定义了一个方法而不在其中进行任何实现“void makePayment();”,同时在将实现接口的类中定义方法的实现。
有人能解释一下为什么这个例子中的抽象类不是带有包装方法的具体类,然后使用带有价格字段的单独接口吗?既然我们可以一起扩展和实现呢?
需要将“如果我们添加方法(具体/抽象)”更正为“如果我们添加具体方法”。
S
SkyWalker

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 接口更改包括接口中的静态方法和默认方法。在 Java 8 之前,我们只能在接口中声明方法。但是从 Java 8 开始,我们可以在接口中使用默认方法和静态方法。引入 Default Method 之后,似乎接口和抽象类是一样的。但是,它们在 Java 8 中仍然是不同的概念。抽象类可以定义构造函数。它们更加结构化,并且可以具有与之关联的状态。相比之下,默认方法只能在调用其他接口方法的情况下实现,而不参考特定实现的状态。因此,两者都用于不同的目的并在两者之间进行选择实际上取决于场景上下文。

1.1.2。概念差异:

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

因此,当抽象类被有效地简化为低可见性的接口骨架实现时,默认方法是否也可以消除这一点?果断:不!实现接口几乎总是需要一些或所有默认方法所缺乏的类构建工具。如果某些接口没有,这显然是一个特例,不应该让你误入歧途。

1.2 Java 8 中的接口默认方法

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

让我们考虑一个小例子来了解它是如何工作的:

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。使用默认方法解决了 ForEach 方法编译错误

对于 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 接口中添加 Stream 而不会破坏实现类。

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  {
}

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

java:Impl 类从类型 InterfaceA 和 InterfaceB 继承了 defaultMethod() 的不相关默认值

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

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

此外,如果我们想调用任何超级接口提供的默认实现而不是我们自己的实现,我们可以这样做:

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

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

1.2.4。关于java接口默认方法的要点:

Java 接口默认方法将帮助我们扩展接口,而不必担心破坏实现类。 Java 接口默认方法弥合了接口和抽象类之间的差异。 Java 8 接口默认方法将帮助我们避免使用实用程序类,例如所有 Collections 类方法都可以在接口本身中提供。 Java 接口默认方法将帮助我们移除基实现类,我们可以提供默认实现,实现类可以选择覆盖哪一个。在接口中引入默认方法的主要原因之一是增强 Java 8 中的 Collections API 以支持 lambda 表达式。如果层次结构中的任何类具有具有相同签名的方法,则默认方法变得无关紧要。默认方法不能覆盖 java.lang.Object 中的方法。推理很简单,因为 Object 是所有 java 类的基类。因此,即使我们将 Object 类方法定义为接口中的默认方法,它也将毫无用处,因为将始终使用 Object 类方法。这就是为什么为了避免混淆,我们不能使用覆盖 Object 类方法的默认方法。 Java 接口默认方法也称为 Defender 方法或虚拟扩展方法。

资源链接:

何时使用:Java 8+ 接口默认方法与抽象方法 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() 方法,则会导致编译器错误。

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

接口 Null 检查 Impl Null 检查

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

Impl Null Check MyData Print:: Impl Null Check

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

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

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

Java接口静态方法是接口的一部分,我们不能将它用于实现类对象。 Java 接口静态方法非常适合提供实用方法,例如空值检查、集合排序等。Java 接口静态方法通过不允许实现类覆盖它们来帮助我们提供安全性。我们不能为 Object 类方法定义接口静态方法,我们会得到编译器错误“这个静态方法不能从 Object 中隐藏实例方法”。这是因为它在 java 中是不允许的,因为 Object 是所有类的基类,我们不能拥有一个类级别的静态方法和另一个具有相同签名的实例方法。我们可以使用java接口的静态方法来移除Collections等实用类,并将其所有的静态方法移到对应的接口中,这样便于查找和使用。

1.4 Java 函数式接口

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

引入了一个新注释 @FunctionalInterface 以将接口标记为功能接口。 @FunctionalInterface 注释是一种避免在功能接口中意外添加抽象方法的工具。使用它是可选的,但很好的做法。

函数式接口是 Java 8 期待已久且备受追捧的特性,因为它使我们能够使用 lambda 表达式来实例化它们。添加了一个带有一堆功能接口的新包 java.util.function 来为 lambda 表达式和方法引用提供目标类型。我们将在以后的文章中研究函数式接口和 lambda 表达式。

资源位置:

Java 8 接口变化——静态方法,默认方法


我正在寻找这些类型的更新答案。感谢您的快速回复。
非常彻底,但就采访而言,我认为事情变得更糟了!可怜的面试官只是想知道这家伙是否可以将他的知识应用到实际情况中,而不是关于该主题的百科全书!
R
Ravindra babu

除了您的第一条语句(Java 8 发布之后)之外,您的所有语句都是有效的:

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

从文档 page

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

默认方法:

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

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

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

根本不提默认方法,它让你的扩展接口继承默认方法。重新声明默认方法,使其抽象化。重新定义覆盖它的默认方法。

静态方法:

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

这使您可以更轻松地在库中组织辅助方法;

文档页面中关于 interface 具有 staticdefault 方法的示例代码。

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));
    }
}

使用以下指南来选择是使用接口还是抽象类。

界面:

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

抽象类:

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

相关文章:

Interface vs Abstract Class (general OO)

Implements vs extends: When to use? What's the difference?

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

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


你说的“无国籍合同”是什么意思?这是关于接口的第 1 项
不存在可变状态。由于接口可以具有常量,因此数据可以与抽象类不同
更正上述陈述。在接口中,数据不能像抽象类那样被改变
这是最好的答案。它不仅针对 Java8,而且还解释了在什么特定情况下您将使用其中任何一种。
界面中 stateless 的概念很受欢迎。接口不能有任何状态(接口可以有常量,但它们是最终/静态的,因此是不可变的)。
V
VictorCreator

你的解释看起来不错,但可能看起来你是从教科书中读到的? :-/

我更担心的是,你的例子有多扎实?您是否费心将抽象和接口之间的几乎所有差异都包括在内?

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

对于差异的详尽列表..

希望它对您和所有其他读者在未来的采访中有所帮助


分享的链接真的很棒
您可以使用 default 关键字在 java 接口中提供默认实现
正如@Ogen 所提到的,该表就默认实现(接口)单元格而言已过时。
S
Sergiu Dumitriu

许多初级开发人员错误地将接口、抽象类和具体类视为同一事物的细微变化,并纯粹出于技术原因选择其中之一:我需要多重继承吗?我需要一些地方来放置常用方法吗?除了具体的课程之外,我还需要为其他事情烦恼吗?这是错误的,隐藏在这些问题中的是主要问题:“我”。当您自己编写代码时,您很少会想到其他现在或未来的开发人员正在处理或使用您的代码。

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

概括

接口定义了一些实现将为您完成的合同。抽象类提供您的实现可以重用的默认行为。

上面这两点是我面试的时候要找的,是一个足够紧凑的总结。请阅读以获得更多详情。

替代摘要

接口用于定义公共 API 抽象类用于内部使用和定义 SPI

举例

换句话说:一个具体的类以一种非常具体的方式完成实际的工作。例如,ArrayList 使用连续的内存区域以紧凑的方式存储对象列表,它提供快速的随机访问、迭代和就地更改,但在插入、删除甚至偶尔添加时都很糟糕;同时,LinkedList 使用双链接节点来存储对象列表,它提供了快速迭代、就地更改和插入/删除/添加,但在随机访问时很糟糕。这两种类型的列表针对不同的用例进行了优化,如何使用它们非常重要。当您试图从您经常与之交互的列表中挤出性能时,并且在选择列表类型取决于您时,您应该仔细选择您要实例化的列表。

另一方面,列表的高级用户并不真正关心它是如何实际实现的,他们应该远离这些细节。让我们想象一下,Java 没有公开 List 接口,而只有一个具体的 List 类,这实际上就是现在的 LinkedList。所有 Java 开发人员都会定制他们的代码以适应实现细节:避免随机访问,添加缓存以加快访问速度,或者只是自己重新实现 ArrayList,尽管它与实际使用的所有其他代码不兼容仅限 List。那将是可怕的......但现在想象一下,Java 大师们实际上意识到链表对于大多数实际用例来说是可怕的,并决定为他们唯一可用的 List 类切换到数组列表。这将影响世界上每个 Java 程序的性能,人们不会对此感到高兴。罪魁祸首是实现细节是可用的,开发人员认为这些细节是他们可以依赖的永久合同。这就是为什么隐藏实现细节并只定义抽象合约很重要的原因。这就是接口的目的:定义方法接受什么样的输入,以及期望什么样的输出,而不暴露所有诱使程序员调整代码以适应可能随着任何未来更新而改变的内部细节的胆量.

抽象类位于接口和具体类之间。它应该帮助实现共享通用或无聊的代码。例如,AbstractCollection 根据大小为 0、contains 为迭代和比较、addAll 为重复 add 等为 isEmpty 提供基本实现。这让实现可以专注于区分它们的关键部分:如何实际存储和检索数据。

另一个视角:API 与 SPI

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

抽象类是在实现接口时使用的高内聚帮助器,假设有一定程度的实现细节。或者,抽象类用于定义 SPI、服务提供者接口。

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

向 API 添加方法很容易,API 的所有现有用户仍然可以编译。向 SPI 添加方法很困难,因为每个服务提供者(具体实现)都必须实现新方法。如果使用接口来定义 SPI,则只要 SPI 合同发生变化,提供者就必须发布新版本。如果改为使用抽象类,则可以根据现有抽象方法定义新方法,也可以将其定义为空的 throw not implemented exception 存根,这至少允许旧版本的服务实现仍然可以编译和运行。

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

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

《书本知识》

OP 答案中提供的技术细节被认为是“书籍知识”,因为这通常是学校和大多数关于语言的技术书籍中使用的方法:事物是什么,而不是如何在实践中使用它,尤其是在大规模应用程序中.

这是一个类比:假设问题是:

舞会之夜,汽车或酒店房间租什么更好?

技术答案听起来像:

好吧,在汽车上你可以更快地做到这一点,但在酒店房间里你可以更舒适地做到这一点。另一方面,酒店房间只有一个地方,而在车上你可以在更多地方做,比如说你可以去远景点看风景,或者在汽车影院,或许多其他地方,甚至不止一个地方。另外,酒店房间有淋浴。

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


您指的是 “low-coupling” 吗?
@ user2418306 不,内聚是一个更通用的术语,其中包括耦合,尽管它们是接近的同义词,任何一个术语都可以工作。
N
Niklas Rosencrantz

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

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

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

public class Adventure extends JFrame implements Playable

public class Dungeon3D extends SimpleApplication implements Playable

public class Main extends SimpleApplication implements AnimEventListener, ActionListener, Playable

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

public abstract class World...

    public Playable owner;

    public Playable getOwner() {
        return owner;
    }

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

a
akohout

如何思考以下方式:

类和抽象类之间的关系是“is-a”类型

类和接口之间的关系是“has-a”类型

所以当你有一个抽象类Mammals,一个子类Human,和一个接口Driving,那么你可以说

每个人都是哺乳动物

每个人都有一个驾驶(行为)

我的建议是书本知识短语表明他想听听两者之间的语义差异(就像这里已经建议的其他人一样)。


R
Ravindra babu

抽象类不是纯粹的抽象,因为它包含具体(实现的方法)以及未实现的方法。但是接口是纯粹的抽象,因为只有未实现的方法而不是具体的方法。

为什么选择抽象类?

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

为什么是接口?

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


佚名

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


S
Steve Chambers

接口就像一组被公开记录的基因,它们具有某种影响:DNA 测试会告诉我是否有它们——如果有,我可以公开表明我是“携带者” “而我的部分行为或状态将符合它们。 (但当然,我可能有许多其他基因提供了超出此范围的特征。)

抽象类就像single-sex species(*)的死去的祖先:她无法复活,但活着的(即非抽象)后代继承她所有的基因。

(*) 延伸这个比喻,假设该物种的所有成员都活到相同的年龄。这意味着一个死去的祖先的所有祖先也必须是死的——同样,一个活着的祖先的所有后代都必须是活着的。


C
Community

我为工作进行面试,我也会对你的回答不满意(对不起,但我很诚实)。听起来您确实已经阅读了差异并修改了答案,但也许您从未在实践中使用过它。

一个很好的解释为什么你会使用每个比对差异有一个精确的解释要好得多。雇主最终希望程序员做他们不了解的事情,这在面试中很难证明。如果申请基于技术或文档的工作而不是开发人员角色,您给出的答案会很好。

祝以后面试顺利。

我对这个问题的回答更多的是关于面试技巧,而不是你提供的技术材料。也许考虑阅读它。 https://workplace.stackexchange.com/ 可能是处理这类事情的好地方。


你能告诉我你是怎么回答的吗?也许它可以帮助我。
给你答案比帮助你解决问题要少得多,基本上给出一个实际例子,说明你什么时候使用它们,并解释为什么每一个都适合不同的任务。
T
The Rabbit of No Luck

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

通过类层次结构继承意味着状态继承;

而通过接口继承代表行为继承;

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

当然,从 Java 8 开始,事情发生了一些细微的变化,但想法还是一样的。

如果你没有接受编译器团队的面试,我想这对于典型的 Java 面试来说已经足够了。


这简洁明了,传达了两者的目的。
u
user2822053

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

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

click here to watch tutorial on interfaces and abstract classes


v
vinod bazari

即使我在多次面试中都遇到过同样的问题,相信我,说服面试官会让你的时间很痛苦。如果我继承了上面的所有答案,那么我需要再添加一个关键点,以使其更具说服力并充分利用 OO

如果您不打算对规则进行任何修改,对于要遵循的子类,在很长一段时间内,请选择接口,因为您将无法在其中进行修改,如果您这样做,您需要选择所有其他子类都发生了变化,而如果您认为要重用该功能,设置一些规则并使其开放以供修改,请选择抽象类。

以这种方式思考,您使用了可消费的服务,或者您已经向世界提供了一些代码,并且您有机会修改某些内容,假设进行安全检查并且如果我是代码的消费者并且在更新后的一天早上,我在我的 Eclipse 中找到所有读取标记,整个应用程序都已关闭。所以为了防止这样的噩梦,在接口上使用抽象

我认为这可能会在一定程度上说服面试官……Happy Interviews Ahead。


T
Teto

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

当我试图定义一个类型时,我的对象的用户可以可靠地调用的方法列表,然后我创建一个接口。

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

这可能看起来也有点太书呆子了(尽管我从未见过它在我记得的任何地方都是这样的)。如果面试官(或 OP)真的想要更多我的个人经验,我会准备好接口的轶事是出于必要而发展的,反之亦然。

还有一件事。 Java 8 现在允许您将默认代码放入接口中,进一步模糊了接口和抽象类之间的界限。但据我所知,即使是 Java 核心库的制造商,该功能也被过度使用。添加了该功能,并且正确地添加了该功能,以便可以在不造成二进制不兼容的情况下扩展接口。但是如果你通过定义一个接口来创建一个全新的类型,那么接口应该只是一个接口。如果您还想提供通用代码,那么一定要创建一个辅助类(抽象或具体)。不要从一开始就将您的界面与您可能想要更改的功能混为一谈。


u
user207421

您选择 Java 中的接口以避免 Diamond Problem in multiple inheritance

如果您希望您的所有方法都由您的客户端实现,您可以选择接口。这意味着您以抽象的方式设计整个应用程序。

如果您已经知道共同点,则选择抽象类。例如,采用抽象类 Car。在更高级别上,您可以实现常见的汽车方法,例如 calculateRPM()。这是一种常见的方法,您让客户实现他自己的行为,例如
calculateMaxSpeed() 等。您可能会通过提供一些您在日常工作中遇到的实时示例来进行解释。


R
Rodney P. Barbati

为了将其简化为您可以在面试中提供的简单合理的回答,我提供以下内容...

接口用于为一系列相关类指定 API - 关系就是接口。通常用于具有多个实现的情况,通过配置或在运行时选择正确的实现。 (除非使用 Spring,此时接口基本上是 Spring Bean)。接口通常用于解决多重继承问题。

抽象类是专门为继承而设计的类。这也意味着多个实现,所有实现都有一些共性(在抽象类中找到)。

如果你想确定它,那么就说抽象类通常实现接口的一部分——工作是你的!


D
Dharisi Suresh

接口和抽象类的基本区别是,接口支持多重继承,抽象类不支持。

在抽象类中,您还可以提供所有抽象方法,如接口。

为什么需要抽象类?

在某些场景下,抽象类在处理用户请求时,并不知道用户的意图。在这种情况下,我们将在类中定义一个抽象方法,并要求扩展该类的用户,请在抽象方法中提供您的意图。在这种情况下,抽象类非常有用

为什么需要接口?

比方说,我有一项工作,我在该领域没有经验。例如,如果你想建造一座建筑物或水坝,那么在这种情况下你会做什么?

您将确定您的要求并根据该要求签订合同。然后调用投标来构建您的项目 谁曾经构建过该项目,应该满足您的要求。但是构建逻辑从一个供应商到另一个供应商是不同的。

在这里,我不关心他们如何构建的逻辑。最终的对象是否满足我的要求,那只是我的重点。

在这里,您的称为接口和构造函数的需求称为实现者。


P
Pang

嗯,现在人们渴望实用的方法,你是对的,但大多数面试官看起来都按照他们目前的要求,想要一个实用的方法。

完成你的答案后,你应该跳到这个例子:

抽象的:

例如,我们有薪水函数,它有一些所有员工共有的参数。然后我们可以有一个名为 CTC 的抽象类,它带有部分定义的方法体,它将被所有类型的员工扩展,并根据他们的额外功能重新定义。对于通用功能。

public abstract class CTC {

    public int salary(int hra, int da, int extra)
    {
        int total;
        total = hra+da+extra;
        //incentive for specific performing employee
        //total = hra+da+extra+incentive;
        return total;
    }
}

class Manger extends CTC
{
}


class CEO extends CTC
{
}

class Developer extends CTC
{   
}

界面

java中的接口允许在不扩展该接口的情况下具有接口功能,并且您必须清楚要在应用程序中引入的功能签名的实现。它会迫使你有定义。对于不同的功能。

public interface EmployeType {

    public String typeOfEmployee();
}

class ContarctOne implements EmployeType
{

    @Override
    public String typeOfEmployee() {
        return "contract";
    }

}

class PermanentOne implements EmployeType
{

    @Override
    public String typeOfEmployee() {
        return "permanent";
    }

}

您也可以通过将 methgos 定义为抽象类来对抽象类进行这种强制活动,现在一个类扩展了抽象类 remin 抽象类,直到它覆盖该抽象函数。


M
Marz

据我了解,一个接口,由最终变量和没有实现的方法组成,由一个类实现,以获得一组相互关联的方法或方法。另一方面,可以包含非最终变量和具有实现的方法的抽象类通常用作指导或作为所有相关或相似类继承自的超类。换句话说,抽象类包含所有子类共享的所有方法/变量。


d
dg_no_9

在抽象类中,您可以编写方法的默认实现!但在界面中你不能。基本上,在接口中存在必须由实现接口的类实现的纯虚方法。


J
Jaxian

是的,您的回答在技术上是正确的,但是您出错的地方并没有向他们表明您了解选择一个而不是另一个的好处和坏处。此外,他们可能担心/害怕他们的代码库与未来升级的兼容性。这种类型的回应可能有帮助(除了你所说的):

“选择抽象类而不是接口类取决于我们对代码未来的预测。抽象类允许更好的前向兼容性,因为您可以在不破坏现有代码的情况下继续向抽象类添加行为—— > 这对于接口类是不可能的。另一方面,接口类比抽象类更灵活。这是因为它们可以实现多个接口。问题是 Java 没有多重继承,所以使用抽象类不会让你使用任何其他类层次结构......所以,最后一个好的一般经验法则是:当你的代码库中没有现有/默认实现时,更喜欢使用接口类。并且,如果你知道,请使用抽象类来保持兼容性你将来会更新你的课程。”

祝你下次面试好运!


s
sactiw

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

接口的负载为零,即无需维护状态,因此将合同(能力)与类关联是更好的选择。

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

现在你可以问如果 Runnable 是一个抽象类怎么办?

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

Runnable 没有与之关联的状态,也没有为 run() 方法“提供”任何默认实现

任务必须扩展它,因此它不能扩展任何其他类

Task 没有什么可以提供作为 Runnable 类的特化,它所需要的只是覆盖 run() 方法

换句话说,Task 类需要一种在线程中运行的能力,它通过实现 Runnable 接口来实现,而不是扩展 Thread 类使其成为线程。

简单地说,我们用接口来定义一个能力(契约),同时使用一个抽象类来定义它的骨架(通用/部分)实现。

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

interface Forgiver {
    void forgive();
}

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

现在,您可以选择成为 GodLike,但您可以选择仅成为 Forgiver(即不是 GodLike)并执行以下操作:

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

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

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

具有 java 8 接口的 PS 也可以具有静态以及默认(可覆盖实现)方法,因此黑白接口和抽象类的差异更加缩小。


R
Ravindra babu

这里似乎已经涵盖了几乎所有内容。在 abstract 类的实际实现中再添加一点:

abstract 关键字也用于防止类被实例化。如果您有一个不想被实例化的具体类 - 使其抽象。


P
Pang

根据我的理解和我的处理方式,

接口就像一个规范/契约,任何实现接口类的类都必须实现抽象类中定义的所有方法(默认方法除外(Java 8 中引入))

当我知道类的某些方法和某些方法所需的实现时,我定义了一个类抽象,但我仍然不知道实现是什么(我们可能知道函数签名但不知道实现)。我这样做是为了在以后的开发部分中,当我知道如何实现这些方法时,我可以扩展这个抽象类并实现这些方法。

注意:除非方法是静态或默认的,否则接口方法中不能有函数体。


D
Derek Hill

这是一个以 Java 8 为中心的解释,它试图展示抽象类和接口之间的主要区别,并涵盖了 Java Associate Exam 所需的所有细节。

关键概念:

一个类只能扩展一个类,但它可以实现任意数量的接口

接口定义类做什么,抽象类定义它是什么

抽象类是类。它们不能被实例化,但在其他方面表现得像普通类

两者都可以有抽象方法和静态方法

接口可以有默认方法和静态最终常量,并且可以扩展其他接口

所有接口成员都是公共的(直到 Java 9)

接口定义类做什么,抽象类定义它是什么

Roedy Green

接口通常用于描述一个类的能力,而不是它的中心标识,例如,一个汽车类可能实现 Recyclable 接口,它可以应用于许多不相关的对象。抽象类定义了其后代的核心身份。如果你定义了一个 Dog 抽象类,那么 Dalmatian 的后代就是 Dogs,它们不仅仅是可狗的。

在 Java 8 之前,@Daniel Lerps’s answer 很明显,接口就像实现类必须履行的契约。

现在,使用默认方法,它们更像是 Mixin,仍然强制执行合同,但也可以提供代码来完成工作。这允许接口接管抽象类的一些用例。

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

抽象类是类

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

实例变量/非最终变量。因此……

可以访问和修改对象状态的方法

私有/受保护成员(但请参阅 Java 9 的注释)

扩展抽象或具体类的能力

构造函数

关于抽象类的注意事项:

它们不能是最终的(因为它们的整个目的是要扩展的)

扩展另一个抽象类的抽象类继承其所有抽象方法作为它自己的抽象方法

抽象方法

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

是没有主体的方法签名(即没有 {})

在抽象类中必须用 abstract 关键字标记。在接口中这个关键字是不必要的

不能是私有的(因为它们需要由另一个类来实现)

不能是最终的(因为他们还没有实体)

不能是静态的(因为原因)

另请注意:

抽象方法可以被同一个类/接口中的非抽象方法调用

第一个扩展抽象类或实现接口的具体类必须为所有抽象方法提供实现

静态方法

抽象类上的静态方法可以直接用 MyAbstractClass.method(); 调用(即与普通类一样,也可以通过扩展抽象类的类调用)。

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

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

不是默认的(见下文)

默认方法

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

不是静态的

不是抽象的(他们有一个身体)

不能是最终的(名称“default”表示它们可能被覆盖)

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

接口可以有静态最终常量

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

常量假定为 staticfinal,并且可以在实现接口的类中使用而无需限定。

所有接口成员都是公开的

在 Java 8 中,接口的所有成员(以及接口本身)都假定为 public,并且不能是 protectedprivate(但 Java 9 does allow private methods in interfaces)。

这意味着实现接口的类必须定义具有公共可见性的方法(符合不能以较低可见性覆盖方法的正常规则)。


W
Warren Dew

我相信面试官试图理解的可能是接口和实现之间的区别。

代码模块的接口——不是 Java 接口,而是更一般的“接口”——基本上是与使用该接口的客户端代码签订的合同。

代码模块的实现是使模块工作的内部代码。通常,您可以以不止一种不同的方式实现特定接口,甚至可以在客户端代码不知道更改的情况下更改实现。

Java 接口仅应用作上述通用意义上的接口,以定义类如何为使用该类的客户端代码的利益而行为,而无需指定任何实现。因此,接口包括方法签名——名称、返回类型和参数列表——用于期望由客户端代码调用的方法,并且原则上每个方法都应该有大量的 Javadoc 来描述该方法的作用。使用接口最令人信服的原因是,如果您计划有多个不同的接口实现,可能会根据部署配置选择一个实现。

相反,Java 抽象类提供类的部分实现,而不是指定接口的主要目的。当多个类共享代码,但子类也需要提供部分实现时,应该使用它。这允许共享代码仅出现在一个地方 - 抽象类 - 同时明确部分实现不存在于抽象类中并且期望由子类提供。