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


469

在我的一次采访中,我被要求解释InterfaceAbstract类之间的区别。

这是我的回应:

Java接口的方法是隐式抽象的,不能具有实现。Java抽象类可以具有实现默认行为的实例方法。

在Java接口中声明的变量默认为final。抽象类可能包含非最终变量。

默认情况下,Java接口的成员是公共的。Java抽象类可以具有类成员的常用风格,例如private,protected等。

Java接口应使用关键字“ implements”来实现;Java抽象类应使用关键字“ extends”进行扩展。

一个接口只能扩展另一个Java接口,一个抽象类可以扩展另一个Java类并实现多个Java接口。

Java类可以实现多个接口,但只能扩展一个抽象类。

但是,面试官并不满意,并告诉我该描述表示“ 书本知识 ”。

他要求我提供更实际的答复,并通过实际示例说明何时在接口上选择抽象类

我哪里做错了?


32
也许您的答案似乎是您在说一些您不理解的内容?可能只是您需要将讲述的样式更改为更像您自己的单词的那种。
Kirill Kobelev 2013年

19
您回答了(相当正确的)技术差异列表。采访者最有可能寻找一个更具概念性的答案(例如,在使用界面和抽象类之间会选择哪种基础)。
特德·霍普

15
您忘了说抽象类具有构造函数,即使您无法实例化抽象类const。由子类使用。接口表示“什么”,而不表示“如何”,因为它们在抽象时定义了一个合同(方法列表)。类还可以指示“如何”(实现方法)。使用int。您可以模拟多个继承(一个类可以实现多个int,但只能扩展一个类)。使用int。您可以使用dif的基本类型。家庭:传单f = new Plane();传单f2 = new Bird();鸟和飞机并不相同,但是两者都可以飞(都是传单)。
Francisco Goldenstein 2014年

7
从java8开始,接口可以包含方法。因此,除了OO概念之外,这些所谓的“差异”可以随时更改。
环形承载者

15
我对您的回答没有任何问题,并且我认为面试官对“书本知识”不屑一顾。面试官并不总是知道他们所提问题的正确答案,有些面试只是警告您不要在那儿工作。
罗恩侯爵,

Answers:


513

我先给你一个例子:

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

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


97
我不确定这是否能解释差异...确定这是一种不错的技术。我想值得指出的是,Java 8终于承认C ++是正确的,并且可以完成多重继承并且可以使用,因此接口现在不仅可以定义函数签名,还可以提供默认实现。这样,使用接口将是优选的。
thecoshman 2015年

1
@thecoshman如果我按照答案中的方法处理问题(实现了一个方法的抽象类,另一个实现了抽象)或使用默认方法实现定义了接口,那会有什么区别?基本上,我想说的是您写道“使用接口会更可取”,而我的问题是-为什么?
Neutrino

1
因此,我想可以这样说:对于接口,定义的实现取决于实际实现该接口的类,而抽象类中的内容是扩展该类的类的“核心”。即,它不会改变。
orrymr 2016年

4
@Neutrino尽管Java允许您实现多个接口,每个接口都提供函数的默认实现,但您仍然只能扩展单个类。这样,使用接口可以为想要使用它的人以及其他接口提供更大的灵活性。
thecoshman '16

3
@HiradNikoo对不起,您的评论很晚,但是我偶然发现了这个话题。您也可以将类继承视为IS-A关系,而接口表示“具有某种功能”。
亚历山大·扬克

206

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

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

  1. 接口是规则(规则,因为您必须为它们提供不能忽略或避免的实现,以使它们像规则一样被强加),它在软件开发的各个团队中起着共同的理解文件的作用。

  2. 接口给出了要做什么的想法,但没有给出如何实现的想法。因此,遵循给定规则(即给定方法签名)完全取决于开发人员。

  3. 抽象类可以包含抽象声明,具体实现或两者。

  4. 抽象声明就像要遵循的规则,而具体实现就像是准则(可以按原样使用它,也可以通过重写并提供自己的实现来忽略它)。

  5. 此外,具有相同签名的哪些方法可能会在不同上下文中更改行为,将作为接口声明提供为在不同上下文中相应实现的规则。

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


11
+1,这确实是避免混淆的好答案。但是我没有看到任何链接,也不知道为什么要引用那些有价值的行。尽可能将它们作为要点:)。
Suresh Atta

阅读以上有关使用接口模拟多重继承以及使用接口为不同族的类提供基本类型的评论。我认为面试官希望听到被采访者的回答。
Francisco Goldenstein 2014年

您的评论还指出了使用接口的一个很好的例子。我写道,每天工作时的感受。这些话可能不专业或不准确。但是,在我的日常编码中与抽象类和接口紧密合作之后,我才知道了这一点。
Shailesh Saxena 2014年

4.具体实现也是规则,具有默认实现。
Luten 2014年

@Luten:据我所知,如果您可以避免/忽略规则而没有任何问题,那一定是一个准则,而不是规则。如果我错了,请纠正我。
Shailesh Saxena

168

您对使用和实现方面的实际差异进行了很好的总结,但没有对意义上的差异做任何说明。

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

一个抽象类是针对不同的子类份额行为,不需要重复创建的基础。子类必须完成该行为,并且可以选择覆盖预定义的行为(只要未将其定义为finalprivate)。

您将在java.util包中找到很好的示例,其中包括接口(如)List和抽象类(如AbstractList已实现该接口的抽象类)。在官方文档描述AbstractList如下:

此类提供了List接口的基本实现,以最小化实现由“随机访问”数据存储(例如数组)支持的该接口所需的工作。


16
这应该是答案。不是细节列表,而是使接口和抽象类有所不同的基本概念,不仅在Java中,而且在一般情况下。
edc65

1
这真的很好。当然,其他答案也不错。但是,这告诉您有关abstract关键字的主要注意事项,即编译器看到此信息时,他们知道,以下信息是不完整的,需要执行。接口总是不完整的,但是抽象类是抽象的,因为它们必须具有incomplete (abstract)方法。
拉基布

85

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

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

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

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

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


1
嗯,这个答案很有道理,当我们选择在interface和之间进行选择时,该示例就很清楚了abstract class
MAC

在定义方法而不在其中进行任何实现的过程中,“做什么但不知道如何做”“ void makePayment();”,同时在将实现该接口的类中定义该方法的实现。
阿卜杜勒-拉乌夫

45

Abstact类和接口之间的区别

  1. Java 8中的抽象类与接口
  2. 概念差异:

Java 8中的接口默认方法

  1. 什么是默认方法?
  2. 使用默认方法解决了ForEach方法编译错误
  3. 默认方法和多重继承歧义问题
  4. 有关Java接口默认方法的要点:

Java接口静态方法

  1. Java接口静态方法,代码示例,静态方法与默认方法
  2. 有关Java接口静态方法的要点:

Java功能接口



Java 8中的抽象类与接口

Java 8接口更改包括接口中的静态方法和默认方法。在Java 8之前,接口中只能有方法声明。但是从Java 8开始,我们可以在接口中使用默认方法和静态方法。

引入默认方法后,接口和抽象类似乎相同。但是,它们仍然是Java 8中的不同概念。

抽象类可以定义构造函数。它们更加结构化,并且可以具有与之关联的状态。相比之下,默认方法只能在调用其他接口方法方面实现,而无需参考特定实现的状态。因此,两者用于不同目的以及在两者之间进行选择实际上取决于场景上下文。

概念差异:

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

因此,当有效地将抽象类简化为低可见性的接口的基本实现时,默认方法是否也可以消除这种缺陷?决定:不!实现接口几乎总是需要某些或所有默认方法所缺少的类构建工具。而且,如果某些界面没有,那显然是一种特殊情况,这不会使您误入歧途。

Java 8中的接口默认方法

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

让我们考虑一个小示例,以了解其工作原理:

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

默认方法:

默认方法永远不会是最终方法,无法同步,也不能覆盖Object的方法。它们始终是公共的,这严重限制了编写简短且可重用方法的能力。

可以将默认方法提供给接口,而不会影响实现类,因为它包括实现。如果在接口中通过实现定义了每个添加的方法,则不会影响实现类。实现类可以覆盖接口提供的默认实现。

默认方法可以在不破坏这些接口的较早实现的情况下向现有接口添加新功能。

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

  1. 不覆盖默认方法,并将继承默认方法。
  2. 覆盖默认方法,类似于我们在子类中覆盖的其他方法。
  3. 将默认方法重新声明为抽象,这将迫使子类重写它。

使用默认方法解决了ForEach方法编译错误

对于Java 8,JDK集合已得到扩展,并且forEach方法已添加到整个集合(与lambda结合使用)。使用传统方式,代码如下所示,

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

由于此结果导致每个实现的Class都带有编译错误,因此将默认方法与所需的实现一起添加,以便不应该更改现有的实现。

以下是使用默认方法的Iterable接口,

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

在不破坏实现类的情况下,已使用相同的机制在JDK接口中添加Stream


默认方法和多重继承歧义问题

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

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

有关Java接口默认方法的要点:

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

资源链接:

  1. 使用Java 8中的默认方法与Abstract类进行接口
  2. JDK 8时代的抽象类与接口
  3. 通过虚拟扩展方法进行接口演变

Java接口静态方法

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空检查

如果将接口方法从静态设置为默认值,则会得到以下输出。

Impl空检查

MyData打印::

Impl空检查

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

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

有关Java接口静态方法的要点:

  1. Java接口静态方法是接口的一部分,我们不能将其用于实现类对象。
  2. Java接口静态方法非常适合提供实用程序方法,例如null检查,集合排序等。
  3. Java接口静态方法通过不允许实现类覆盖它们来帮助我们提供安全性。
  4. 我们无法为Object类方法定义接口静态方法,我们将得到编译器错误,因为“此静态方法无法从Object隐藏实例方法”。这是因为在Java中是不允许的,因为Object是所有类的基类,并且我们不能有一个类级别的静态方法和另一个具有相同签名的实例方法。
  5. 我们可以使用java接口静态方法删除实用程序类(例如Collections),并将其所有静态方法移动到相应的接口,这将很容易找到和使用。

Java功能接口

在结束本文之前,我想简要介绍一下Functional接口。具有唯一一种抽象方法的接口称为功能接口。

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

功能接口是Java 8期待已久且广受欢迎的功能,因为它使我们能够使用lambda表达式实例化它们。添加了带有功能接口束的新程序包java.util.function,以提供lambda表达式和方法引用的目标类型。在以后的文章中,我们将研究功能接口和lambda表达式。

资源位置:

  1. Java 8接口更改–静态方法,默认方法

8
我正在寻找这些更新的答案类型。感谢您的快速回复。
Ravindra babu

41

除了第一个语句(在Java 8发行版之后)之外,您的所有语句均有效:

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

从文档页面

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

方法主体仅适用于默认方法和静态方法。

默认方法:

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

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

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

  1. 完全不提默认方法,它使您的扩展接口可以继承默认方法。
  2. 重新声明默认方法,使其为abstract
  3. 重新定义默认方法,它将覆盖它。

静态方法:

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

这使您可以更轻松地在库中组织帮助程序方法。

文档页面中有关interfacehaving 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));
    }
}

使用以下准则来选择使用接口还是抽象类。

接口:

  1. 定义合同(最好是无状态的-我的意思是没有变量)
  2. 具有链接不相关类功能。
  3. 声明公共常量变量(不可变状态

抽象类:

  1. 在几个密切相关的类之间共享代码。它建立一个关系。

  2. 相关的类之间共享公共状态(状态可以在具体的类中修改)

相关文章:

接口vs抽象类(通用OO)

实施与扩展:何时使用?有什么不同?

通过查看这些示例,您可以了解

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


你说“无状态合同”是什么意思?关于接口的项目1
Maksim Dmitriev

1
缺乏可变状态。由于接口可以具有常量,因此可以与抽象类不同地对数据进行突变
Ravindra babu

1
以上声明中的更正。在界面中,数据不能像抽象类一样进行突变
Ravindra babu

2
这是最好的答案。它不仅针对Java8,而且还说明了在哪种特殊情况下您将使用其中一种。
Shuklaswag '17

statelessin接口的概念很受欢迎。接口不能具有任何状态(接口可以具有常量,但是它们是最终的/静态的,因此是不可变的)。
开化

22

您的解释看起来不错,但可能看起来就像您正在从教科书中阅读所有内容一样?:-/

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

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

详尽列出差异。

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


1
链接共享真的很棒
Premraj 2015年

您可以使用默认关键字
Ogen,2016年

21

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

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

摘要

  1. 接口定义了一些实现将为实现的合同

  2. 抽象类提供了一个默认的行为您的实现可以重复使用。

以上这两点是我在面试时要寻找的内容,并且摘要非常紧凑。请阅读以获得更多详情。

替代摘要

  1. 接口用于定义公共API
  2. 抽象类供内部使用,并用于定义SPI

举个例子

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

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

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

另一个角度:API与SPI

接口是代码不同部分之间的低凝聚力网关。它们允许库在内部发生变化时不中断每个库用户的情况下存在和发展。它称为应用程序编程接口,而不是应用程序编程类。在较小的规模上,它们还允许多个开发人员通过充分记录的界面分离不同的模块,从而在大型项目上成功进行协作。

假设某种程度的实现细节,抽象类是实现接口时要使用的高凝聚力助手。或者,抽象类用于定义SPI,服务提供商接口。

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

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

关于Java 8和默认方法的注释

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

“书本知识”

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

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

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

技术答案听起来像:

好吧,在汽车上您可以更快地完成此操作,但是在酒店房间中,您可以更加舒适地完成操作。另一方面,酒店房间仅位于一个地方,而在汽车中,您可以在更多地方这样做,比如说,您可以去远景处欣赏美景,或者在露天剧院中,或许多其他地方,甚至不止一个地方。此外,酒店客房还设有淋浴。

的确如此,但是完全错失了它们是两个完全不同的东西,并且两者可以同时用于不同目的的观点,并且“做到这一点”方面对于这两个选项中的任何一个都不是最重要的。 。答案缺乏远见,它显示了一种不成熟的思维方式,同时正确地提出了真实的“事实”。


您是说“低耦合”吗?
user2418306

@ user2418306不,凝聚力是一个更通用的术语,其中包括耦合,尽管它们是紧密的同义词,而且任一个都可以。
Sergiu Dumitriu

9

考虑以下方式:

  • 类和抽象类之间的关系的类型为“ is-a”
  • 类和接口之间的关系的类型为“ has-a”

因此,当您拥有哺乳动物的抽象类,人的子类以及接口Driving时,您可以说

  • 每个人都是哺乳动物
  • 每个人都有一个驾驶(行为)

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


9

接口是一个“合同”,其中实现合同的类承诺实现这些方法。当我将游戏从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;
    }

6

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

为什么要抽象类?

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

为什么要接口?

  1. 如果用户要编写不同的功能,那么对象上的功能将不同。
  2. 界面是发布界面后,如果不需要修改需求的最佳选择。

5

一个界面就像是一组被公开记录有某种作用的基因:一个DNA测试会告诉我,我是否已经得到了他们-如果我这样做,我可以公开让他们知道,我是一个“载体”,而我的部分行为或状态将与他们保持一致。(但是,当然,我可能还有许多其他基因可以提供超出此范围的特征。)

一个抽象类是像的死祖先单性物种(*):她不能被赋予生命,而是活的(即非抽象)的后裔继承了她所有的基因。

(*)为了扩大这个比喻,假设该物种的所有成员都生活在同一年龄。这意味着已故祖先的所有祖先也必须都已死亡-同样,活着祖先的所有后代必须还活着。


4

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

关于为什么要使用每种方法的一个很好的解释可能比对它们之间的区别有一个精确的解释要好得多。雇主终极希望程序员做一些他们不了解的事情,而这在面试中很难证明。如果申请基于技术或文档的工作,而不是开发人员的工作,那么您给出的答案将是很好的。

祝您将来有机会接受采访。

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


1
你能告诉我你如何回答吗?也许可以帮到我。
code_fish

为您提供答案所提供的帮助远远少于帮助您解决问题的方法,基本上给出了一个实际的示例,说明何时使用每种解决方案,并解释了每种解决方案适合于不同任务的原因。
阿德里安

4

选择Java中的Interface可以避免多重继承中Diamond问题

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

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



3

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


3

甚至我在多次面试中都遇到了同样的问题,并相信我,这使您无法说服面试官。如果我从上面得到所有答案,那么我需要再增加一个要点,以使其更有说服力,并以最佳状态使用OO。

如果您不打算在规则中进行任何修改,则要长期遵循该子类,请继续使用该接口,因为您将无法对其进行修改,如果需要这样做,则需要进行该修改。在所有其他子类中进行了更改,而您想重用该功能,设置一些规则并使其可以进行修改,则可以选择Abstract类。

以这种方式思考,您曾经使用过易耗性服务,或者向世界提供了一些代码,并且有机会修改某些内容,假设进行了安全检查。如果我是代码的使用者,并且在更新后的一个早晨,我在我的Eclipse中找到所有已读标记,整个应用程序已关闭。因此,为避免此类噩梦,请在接口上使用Abstract

我认为这可能会在一定程度上说服访调员。。。


2

当我试图在两个紧密相关的类之间共享行为时,我创建了一个抽象类,该抽象类拥有共同的行为并充当这两个类的父级。

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

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

这似乎也有点书呆子(尽管我从未见过它把它放在我记得的任何地方)。如果面试官(或OP)真的希望在此方面获得更多我的个人经历,那么我将已经准备好界面的轶事是出于必要而发展的,反之亦然。

还有一件事。 Java 8现在允许您将默认代码放入接口,从而进一步模糊了接口和抽象类之间的界限。但是据我所见,该功能甚至被Java核心库的制造商过度使用。正确地添加了该功能,以便可以扩展接口而不会产生二进制不兼容。但是,如果要通过定义接口来创建全新的Type,则该接口应该只是一个接口。如果您还想提供通用代码,则一定要创建一个辅助类(抽象或具体)。从一开始就不要在您可能要更改的功能上弄乱您的界面。


2

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

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

为什么需要抽象类?

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

为什么需要接口?

假设我有一份在该领域没有经验的工作。例如,如果您要建造建筑物或水坝,那么在这种情况下您将要做什么?

  1. 您将确定您的要求,并与这些要求订立合同。
  2. 然后致电招标人来构建您的项目
  3. 谁来建设项目,那应该满足您的要求。但是,一个供应商之间的构造逻辑是不同的。

在这里,我不必理会它们如何构造的逻辑。最后一个对象是否满足我的要求,那仅仅是我的重点。

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


2

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

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

可以将抽象类视为这两种情况之间的某种东西(它引入了一些状态,但也使您必须定义行为),完全抽象的类是接口(这是对类的进一步开发,仅在C ++中由虚拟方法组成,如下所示)据我所知,它的语法)。

当然,从Java 8开始,情况有所变化,但是想法仍然相同。

如果您不接受编译器团队的采访,我想这对于典型的Java采访就足够了。


1

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


1

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


1

是的,您的回答在技术上是正确的,但是您出了错的地方并没有表明他们理解选择一个选项的优点和缺点。此外,他们将来可能会担心/害怕其代码库与升级的兼容性。此类回复可能有所帮助(除了您所说的):

“通过接口类选择抽象类取决于我们对代码未来的预测。

抽象类提供了更好的前向兼容性,因为您可以在将来继续向抽象类中添加行为,而又不会破坏现有的代码->接口类不可能做到这一点。

另一方面,接口类比抽象类更灵活。这是因为它们可以实现多个接口。问题是Java没有多重继承,因此使用抽象类不会让您使用任何其他类层次结构。

因此,最后一个良好的通用经验法则是:当您的代码库中没有现有/默认实现时,请首选使用接口类。而且,如果您知道将来会更新您的类,请使用抽象类来保持兼容性。”

祝您下次面试顺利!


1

我将尝试使用实际方案来回答这两者之间的区别。

接口的净荷为零,即无需维护任何状态,因此是将合同(功能)与类关联的更好选择。

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

现在,您可以询问Runnable是否是抽象类?

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

  • Runnable没有与之关联的状态,也没有为run()方法提供任何默认实现
  • 任务必须扩展它,因此它不能扩展任何其他类
  • 任务没有提供对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    
    }
}

或者您可以选择成为GodLike并执行以下操作:

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

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


1

这里似乎几乎已经涵盖了所有内容。.在abstract类的实际实现上仅增加了一点:

abstract关键字也用于防止类被实例化。如果您有一个具体的类不想被实例化-制作它abstract


1

嗯,现在人们都渴望实用的方法,您说的很对,但是大多数面试官都根据他们当前的要求,并希望采用实用的方法。

完成回答后,您应该跳到以下示例:

抽象:

例如,我们有工资功能,所有员工都有一些参数。那么我们可以有一个名为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";
    }

}

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


1

从我的理解以及我的方法,

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

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

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


0

我相信,面试官试图弄清的可能是界面和实现之间的差异。

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

代码模块的实现是使模块工作的内部代码。通常,您可以通过一种以上不同的方式来实现特定的接口,甚至可以更改实现,而无需客户端代码,即使知道更改也是如此。

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

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


0

您的答案是正确的,但访问员需要您根据软件工程的角度而不是根据Java的细节进行区分。

简单的话:

接口就像商店的接口一样,商店上显示的所有内容都应该在商店中,因此接口中的任何方法都必须在具体类中实现。现在,如果某些类共享某些确切的方法而其他方法有所不同该怎么办。假设接口是关于一个包含两件东西的商店,并且假设我们有两个商店都包含运动器材,但其中一个拥有额外的衣服,另一个拥有额外的鞋子。因此,您要做的是为Sport创建一个抽象类,该类实现了Sports方法,而另一个方法未实现。这里的抽象类意味着该商店本身并不存在,但它是其他类/商店的基础。这样,您可以组织代码,避免复制代码,统一代码并确保其他类的可重用性。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.