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

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

以下是我的回答:

Java接口的方法隐式抽象 并且不能有实现。Java抽象类可以有 实现默认行为的实例方法 Java接口中声明的变量默认为final。一个 抽象类可以包含非最终变量

Java接口的成员默认是公共的。Java摘要 类可以有常见的类成员,比如private, 保护等。< / p > Java接口应该使用关键字“implements”来实现;一个 Java抽象类应该使用关键字" extends "进行扩展 一个接口只能扩展另一个Java接口,一个抽象类 可以扩展另一个Java类并实现多个Java接口 Java类可以实现多个接口,但只能扩展 一个抽象类

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

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

我哪里错了?

471833 次浏览

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

我更关心的是,你的例子有多可靠?你有没有把抽象和接口之间的所有区别都包括在几乎中?

就我个人而言,我会建议这个链接: # EYZ0 < / p >

对于差异的详尽列表..

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

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

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

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

# EYZ0

# EYZ0

公共类Main扩展SimpleApplication实现AnimEventListener, ActionListener,播放< /代码> < / p >

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

public abstract class World...


public Playable owner;


public Playable getOwner() {
return owner;
}


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

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

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

  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时,并不强制为interface的默认方法提供实现。

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

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

你很好地总结了使用和实现方面的实际差异,但没有提到意义上的差异。

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

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

你会在java.util包中找到很好的例子,它包含了像List这样的接口,以及像AbstractList这样已经实现了接口的抽象类。官方文档AbstractList的描述如下:

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

我为工作做面试,我也会不看好你的答案(抱歉,但我很诚实)。听起来你确实读过不同之处并修改了答案,但也许你从未在实践中使用过。

一个好的解释为什么你会使用每一种,比一个精确的解释差异要好得多。雇主最终希望程序员做一些他们不了解的事情,而这些事情很难在面试中展示出来。如果你申请的是技术或文档相关的工作,而不是开发人员的职位,你给出的答案会很好。

祝你以后面试顺利。

另外,我对这个问题的回答更多的是关于面试技巧,而不是你提供的技术材料。或许可以考虑读一读。https://workplace.stackexchange.com/可能是这类事情的绝佳场所。

我先给大家举个例子:

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

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

在Java中选择Interface以避免多重继承中的钻石问题

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

如果您已经知道哪些是共同的,则选择抽象类。例如,取一个抽象类Car。在更高的级别上实现常见的car方法,如calculateRPM()。这是一个常见的方法,你让客户端实现他自己的行为,如
# EYZ0等等。也许你会举几个你在日常工作中遇到的真实例子来解释。< / p >

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

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

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

根据我的理解,一个Interface是由最终变量和没有实现的方法组成的,它是通过一个类来实现的,以获得一组或一组相互关联的方法。另一方面,抽象类可以包含非最终变量和具有实现的方法,通常用作指导或超类,所有相关或类似的类都从其继承。换句话说,抽象类包含其所有子类共享的所有方法/变量。

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

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

这里似乎已经涵盖了几乎所有的东西。再补充一点关于abstract类的实际实现:

abstract关键字还用于防止类被实例化。如果你有一个具体的类,你不想被实例化-让它abstract

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

为什么是抽象类?

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

为什么接口?

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

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

回答完你的问题后,你应该跳到例子上:

文摘:

,例如,我们有工资函数,它有一些对所有员工共同的参数。然后我们可以有一个叫做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定义为抽象类,你也可以对抽象类进行这样的强制活动,现在一个扩展抽象类的类仍然是抽象类,直到它覆盖抽象函数。

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

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

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

不妨这样想:

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

当你有一个抽象类哺乳动物,一个子类人类,和一个接口驱动,然后你可以说

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

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

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

如果你不是计划规则中的任何修改,对于子类要遵循,在很长一段时间内,去接口,因为你不能在其中修改,如果你这样做,你需要去所有其他子类的变化,然而,如果你认为,您希望重用该功能,设置一些规则,并允许对其进行修改,去抽象类。

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

上使用抽象

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

你的所有语句都是有效的,除了你的第一个语句(在Java 8发布之后):

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

从文档页面:

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

方法体只存在于默认方法和静态方法中。

默认的方法:

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

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

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

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

静态方法:

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

这让你更容易在你的库中组织帮助方法;

文档页中关于interfacestaticdefault方法的示例代码。

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

使用下面的指导原则来选择是使用接口还是抽象类。

< em >接口:< / em >

  1. 定义合同< em > < / em >(最好是无状态的——我的意思是没有变量)
  2. < em >有一个< / em >功能链接不相关的类。
  3. 声明公共常量变量(< / em > < em >不变的状态)

< em >抽象类:< / em >

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

  2. 相关类(状态可在具体类中修改)

相关文章:

接口vs抽象类(通用OO

Implements vs extends:什么时候使用?有什么不同?< / >

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

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

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中,我们可以有默认方法和

.接口中的静态方法 在介绍了默认方法之后,似乎接口和 抽象类是一样的。然而,它们仍然是不同的概念 在Java 8中,

抽象类可以定义构造函数。他们更有条理 可以有一个与它们相关联的状态。相反,违约 方法只能以调用other的方式实现 接口方法,不引用特定的实现 状态。因此,两者都用于不同的目的,并在两者之间进行选择

1.1.2. 概念上的差别:

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

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

1.2 Java 8中的接口默认方法

Java 8引入了“默认的方法”或(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. # EYZ0

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

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

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

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

  1. 不重写默认方法,将继承默认方法。
  2. 覆盖默认方法,类似于我们覆盖的其他方法 李子类。< / >
  3. 将默认方法重新声明为抽象,强制子类 李重写它。< / >

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 Interface中添加,而不破坏实现类。


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()的不相关默认值 types InterfaceA and InterfaceB

. types类型

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

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接口默认方法的要点:

  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接口默认方法也被称为防御方法或虚拟扩展方法。

资源链接:

  1. 何时使用:Java 8+接口的默认方法,vs.抽象方法
  2. JDK 8时代的抽象类与接口
  3. 通过虚拟扩展方法实现接口演化 . Java 8接口更改-静态方法,默认方法

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

# EYZ0

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

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

Java 8现在允许您将默认代码放入接口中,进一步模糊了接口和抽象类之间的界限。但据我所见,即使是Java核心库的创建者也过度使用了该特性。添加该特性是为了能够在不产生二进制不兼容性的情况下扩展接口。但是如果你通过定义一个接口来创建一个全新的Type,那么这个接口应该只是一个接口。如果还想提供公共代码,那么务必创建一个helper类(抽象的或具体的)。不要从一开始就用你想要修改的功能把界面弄得乱七八糟。

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

抽象类就像单性物种(*)的死去的祖先:她不能复活,但一个活着的后代(即非抽象)继承了她所有的基因。

为了扩展这个比喻,我们假设这个物种的所有成员都活到相同的年龄。这意味着一个死去的祖先的所有祖先也必须是死的——同样,一个活着的祖先的所有后代也必须是活着的。

根据我的理解和我的做法,

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

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

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

许多初级开发人员错误地认为接口、抽象类和具体类是同一事物的微小变化,并纯粹出于技术原因选择其中之一:这是错误的,隐藏在这些问题中的是主要问题:当您为自己编写代码时,您很少会想到其他现在或将来的开发人员正在使用或使用您的代码。

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

总结

  1. 接口定义了一个契约,某个实现将为你实现

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

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

替代的总结

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

通过例子

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

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

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

另一个角度:api与spi

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

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

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

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

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

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

“书本知识”

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

打个比方:假设问题是:

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

技术上的答案是这样的:

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

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

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

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

祝你下次面试好运!

我相信面试官想要了解的可能是界面和实现之间的区别。

代码模块的接口——不是Java接口,更通用的说法是“接口”——基本上是与使用该接口的客户端代码之间的契约。

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

Java接口只能作为上述一般意义上的接口使用,用于定义类的行为,以使使用该类的客户端代码受益,而不指定任何实现。因此,接口包含了客户端代码期望调用的方法的方法签名(名称、返回类型和参数列表),原则上每个方法都应该有足够的Javadoc来描述该方法的功能。使用接口的最引人注目的原因是,如果您计划拥有该接口的多个不同实现,可能需要根据部署配置选择实现。

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

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

简单的单词:

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

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

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

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

现在你可以问,如果Runnable是一个抽象类呢?

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

  • Runnable没有与它相关的状态,也没有“提供”任何状态 run()方法的默认实现
  • Task必须扩展它,因此它不能扩展任何其他类
  • Task没有提供任何专门化到Runnable类,它所需要的只是重写run()方法

换句话说,Task类需要在线程中运行的能力,这是通过实现Runnable接口而实现的,而扩展thread类则使其成为线程。

简单地把我们的接口定义一个能力(合同),而使用一个 的抽象类,用于定义的框架(公共/部分)实现 它。< / p >

下面是免责声明:愚蠢的例子,尽量不要判断:-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
}
}

注:与java 8接口也可以有静态以及默认(可重写的实现)方法,因此区别b/w接口和抽象类甚至更窄。

接口与抽象类的基本区别在于,接口支持多重继承,而抽象类不支持。

在抽象类中,你也可以提供所有的抽象方法,比如接口。

为什么需要抽象类?

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

为什么需要接口?

比方说,我有一份工作,但我在那方面没有经验。的例子, 如果你想建造一座建筑或大坝,那么在这种情况下你会怎么做?< / p >
  1. 你将确定你的需求是什么,并根据这些需求制定一份合同。
  2. 然后打电话给投标者来构建你的项目
  3. 无论谁建造这个项目,都应该满足你的要求。但是不同厂商的构造逻辑是不同的。

这里我不关心它们是如何构造的。最终的目标是否满足我的要求,这只是我的关键点。

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

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

  • 通过类层次结构继承意味着国家继承;
  • 而通过接口继承则代表行为的继承;

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

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

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

下面是一个围绕Java 8的解释,试图展示抽象类和接口之间的关键区别,并涵盖了Java助理考试所需的所有细节。

关键概念:

  • 一个类只能extend一个类,但是它可以implement任意数量的接口
  • 接口定义了类的功能,抽象类定义了它是什么
  • 它们不能被实例化,但在其他方面表现得像普通类
  • 两者都可以有抽象方法静态方法
  • 接口可以有默认的方法 &静态最终常数,并且可以扩展其他接口
  • 所有接口成员都是公共的(直到Java 9)

接口定义了类的功能,抽象类定义了它是什么

/ # EYZ0:

接口通常用来描述一个类的能力,而不是它的中心标识,例如,一个Automobile类可能实现了可回收接口,它可以应用于许多不相关的对象。抽象类定义其后代的核心标识。如果你定义一个Dog抽象类,那么达尔马提亚的后代就是Dog,他们不仅仅是可狗的。

Pre Java 8, @Daniel Lerps的回答是正确的,接口就像实现类必须履行的契约。

现在,对于默认方法,它们更像Mixin,仍然执行契约,但也可以提供代码来完成这项工作。这使得接口可以接管抽象类的一些用例。

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

抽象类是类

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

  • 实例变量/非最终变量。因此……
  • 可以访问和修改对象状态的方法
  • 私有/受保护成员(但请参阅Java 9的注释)
  • 扩展抽象或具体类的能力
  • 构造函数

关于抽象类需要注意的几点:

  • 它们不能是final(因为它们的全部目的是扩展)
  • 扩展另一个抽象类的抽象类继承其所有抽象方法作为自己的抽象方法

抽象方法

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

  • 是没有正文的方法签名(即没有{})
  • 在抽象类中必须用abstract关键字标记。在接口中,该关键字是不必要的
  • 不能是private(因为它们需要由另一个类实现)
  • 不能是final(因为他们还没有身体)
  • 不能是static (因为的原因)

还要注意:

  • 抽象方法可以由同一类/接口中的非抽象方法调用
  • 扩展抽象类或实现接口的第一个具体类必须为所有抽象方法提供实现

静态方法

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

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

  • 不能是abstract,即必须有一个主体(参见上面的“because reasons”)
  • 不是default(见下文)

默认的方法

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

  • 不是static
  • 不是abstract(他们有身体)
  • 不能是final(名称“default”表示它们可能被覆盖)

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

接口可以有静态的final常量

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

常量被假定为staticfinal,并且可以在实现接口的类中不受限制地使用。

所有接口成员都是公共的

在Java 8中,接口的所有成员(以及接口本身)都被假定为public,而不能是protectedprivate(但Java 9是接口中是否允许私有方法)。

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

为了让你能在面试中给出一个简单、合理的回答,我提供以下几点:

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

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

如果你想解决这个问题,那么就说抽象类经常实现接口的一部分——job就是你的了!