Java中的抽象类与接口


87

我被问到一个问题,我想在这里让我的答案复习。

问:在哪种情况下,扩展抽象类而不是实现接口更合适?

答:如果我们使用模板方法设计模式。

我对么 ?

很抱歉,如果我不能清楚地说明问题。
我知道抽象类和接口之间的基本区别。

1)当需要满足以下条件时使用抽象类:对于特定操作(实现方法),我们需要在每个子类中实现相同的功能,而对于某些其他操作(仅方法签名),则需要实现不同的功能

2)如果需要使签名相同(并且实现不同),请使用接口,以便可以遵循接口实现

3)我们可以扩展一个抽象类的最大值,但是可以实现多个接口

重申一个问题:除了上述提到的情况之外,还有其他情况需要专门使用抽象类吗(可以看到模板方法设计模式仅在概念上基于此)?

接口与抽象类

在这两者之间进行选择确实取决于您要做什么,但幸运的是,对于我们来说,Erich Gamma可以为我们提供一些帮助。

一如既往地需要权衡取舍,接口为您提供了关于基类的自由,抽象类为您提供了以后添加新方法自由。–埃里希·伽玛(Erich Gamma)

不能去更改接口而不必更改代码中的许多其他内容,因此避免这种情况的唯一方法是创建一个全新的接口,这不一定总是一件好事。

Abstract classes应该主要用于紧密相关的对象。Interfaces擅长为不相关的类提供通用功能。




这不是重复的。OP希望知道何时扩展抽象类而不是实现接口。他不想知道何时编写抽象类或接口。他的抽象类和接口已经编写。高清想要知道是扩展还是实施。
Shiplu Mokaddim'4

1
@ shiplu.mokadd.im这是没有区别的区别。您不能在不扩展抽象类的情况下使用它。您在这里挑剔似乎完全没有意义。
罗恩侯爵

Answers:


86

何时使用界面

接口允许某人从头开始来实现您的接口,或以其他原始目的或主要目的与您的接口完全不同的代码来实现您的接口。对他们来说,您的界面只是偶然的,必须添加到他们的代码中才能使用您的包。缺点是接口中的每个方法都必须是公共的。您可能不想公开所有内容。

何时使用抽象类

相反,抽象类提供了更多的结构。它通常定义一些默认实现,并提供一些对于完整实现有用的工具。问题是,使用它的代码必须以您的类为基础。如果其他想要使用您的程序包的程序员已经独立开发了自己的类层次结构,那么这可能会非常不便。在Java中,一个类只能从一个基类继承。

何时同时使用

您可以提供两全其美的方法,一个接口和一个抽象类。实施者可以选择忽略您的抽象类。这样做的唯一缺点是,通过接口名称调用方法比通过抽象类名称调用方法要慢一些。


我认为OP希望知道何时扩展抽象类而不是实现接口
Shiplu Mokaddim

@ shiplu.mokadd.im实际上,OP提出了一个非常具体的问题,答案是“是”或“否”。
罗恩侯爵

4
你是对的。但是在SO中,我们用适当的解释回答是/否。
Shiplu Mokaddim 2012年

1
@ shiplu.mokadd.im我看不到如何让您获得错误陈述他的问题的许可。
罗恩侯爵

仅基于这一单一陈述,If we are using template method design pattern我们不能说YESNO
DivineDesert

31

重申一个问题:除了上述提到的情况之外,还有其他任何情况,我们特别需要使用抽象类(可以看到模板方法设计模式仅在概念上基于此)

是的,如果您使用JAXB。它不喜欢接口。您应该使用抽象类或通过泛型解决此限制。

从个人博客文章中

接口:

  1. 一个类可以实现多个接口
  2. 接口根本无法提供任何代码
  3. 接口只能定义公共静态最终常量
  4. 接口无法定义实例变量
  5. 添加新方法会对实现类产生连锁反应(设计维护)
  6. JAXB无法处理接口
  7. 接口不能扩展或实现抽象类
  8. 所有接口方法都是公开的

通常,应该使用接口来定义合同(要实现什么,而不是如何实现)。

抽象类:

  1. 一个类最多可以扩展一个抽象类
  2. 抽象类可以包含代码
  3. 抽象类可以定义静态常量和实例常量(最终)
  4. 抽象类可以定义实例变量
  5. 修改现有抽象类代码会对扩展类产生连锁反应(实现维护)
  6. 向抽象类添加新方法不会对扩展类产生连锁反应
  7. 抽象类可以实现接口
  8. 抽象类可以实现私有和受保护的方法

抽象类应用于(部分)实现。它们可能是限制API合同实施方式的一种手段。


2
在Java 8接口8中,您也可以具有defaultstatic方法。
新手用户


9

这里有很多很棒的答案,但是我经常发现使用BOTH接口和抽象类是最好的方法。 考虑这个人为的例子:

您是一家投资银行的软件开发人员,需要构建一个将订单投放到市场的系统。您的界面捕获了有关交易系统功能的最一般的想法,

1) Trading system places orders
2) Trading system receives acknowledgements

并可以在界面中捕获 ITradeSystem

public interface ITradeSystem{

     public void placeOrder(IOrder order);
     public void ackOrder(IOrder order);

}

现在,在销售台和其他业务部门工作的工程师可以开始与您的系统交互,以将订单下达功能添加到其现有应用程序中。而且您甚至还没有开始构建!这就是界面的力量。

因此,您可以继续建立适用于股票交易员的系统;他们听说您的系统具有查找便宜股票的功能,并且非常渴望尝试!您通过一种称为的方法来捕获这种行为findGoodDeals(),但同时也意识到在连接市场时涉及到很多混乱的事情。例如,你要打开一个SocketChannel

public class StockTradeSystem implements ITradeSystem{    

    @Override 
    public void placeOrder(IOrder order);
         getMarket().place(order);

    @Override 
    public void ackOrder(IOrder order);
         System.out.println("Order received" + order);    

    private void connectToMarket();
       SocketChannel sock = Socket.open();
       sock.bind(marketAddress); 
       <LOTS MORE MESSY CODE>
    }

    public void findGoodDeals();
       deals = <apply magic wizardry>
       System.out.println("The best stocks to buy are: " + deals);
    }

具体的实现将有很多类似这样的凌乱方法connectToMarket(),但这findGoodDeals()是所有交易者真正关心的。

现在,这里是抽象类起作用的地方。 您的老板告诉您,货币交易员也希望使用您的系统。纵观货币市场,您会发现管道几乎与股票市场相同。实际上,connectToMarket()可以逐字重复使用以连接到外汇市场。但是,findGoodDeals()在货币领域是一个截然不同的概念。因此,在将代码库传递给跨洋的外汇智商之前,您首先要重构为一个abstract类,findGoodDeals()

public abstract class ABCTradeSystem implements ITradeSystem{    

    public abstract void findGoodDeals();

    @Override 
    public void placeOrder(IOrder order);
         getMarket().place(order);

    @Override 
    public void ackOrder(IOrder order);
         System.out.println("Order received" + order);    

    private void connectToMarket();
       SocketChannel sock = Socket.open();
       sock.bind(marketAddress); 
       <LOTS MORE MESSY CODE>
    }

您的股票交易系统会findGoodDeals()按照您已定义的方式实施,

public class StockTradeSystem extends ABCTradeSystem{    

    public void findGoodDeals();
       deals = <apply magic wizardry>
       System.out.println("The best stocks to buy are: " + deals);
    }

但是现在,外汇小子可以通过简单地提供findGoodDeals()货币实现来构建自己的系统。她不必重新实现套接字连接,甚至不必重新实现接口方法!

public class CurrencyTradeSystem extends ABCTradeSystem{    

    public void findGoodDeals();
       ccys = <Genius stuff to find undervalued currencies>
       System.out.println("The best FX spot rates are: " + ccys);
    }

对接口进行编程功能强大,但是相似的应用程序通常以几乎相同的方式重新实现方法。使用抽象类可避免重复,同时保留了接口的功能。

注意:有人可能想知道为什么findGreatDeals()不是界面的一部分。请记住,该接口定义了交易系统的最常规组件。另一位工程师可能会开发一个完全不同的交易系统,而他们并不关心寻找好的交易。该接口保证了销售台也可以与他们的系统接口,因此最好不要将您的接口与诸如“大笔交易”之类的应用程序概念纠缠在一起。


6

您应该使用哪个抽象类或接口?

如果以下任何语句适用于您的用例,请考虑使用抽象类:

您想在几个紧密相关的类之间共享代码。

您期望扩展您的抽象类的类具有许多公共方法或字段,或者需要除public(例如protected和private)之外的访问修饰符。

您要声明非静态或非最终字段。这使您能够定义可以访问和修改它们所属对象的状态的方法。

如果以下任何语句适用于您的用例,请考虑使用接口:

您期望不相关的类将实现您的接口。例如,接口Comparable和Cloneable由许多不相关的类实现。

您想指定特定数据类型的行为,但不关心谁实现了它的行为。

您想利用类型的多重继承。

http://docs.oracle.com/javase/tutorial/java/IandI/abstract.html


4

在过去的三年中,随着与Java 8版本的接口增加了新功能,情况发生了很大变化。

从界面上的oracle文档页面

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

正如您在问题中所引用的,抽象类最适合您必须创建框架的模板方法模式。接口不能在这里使用。

与接口相比,更喜欢抽象类的另一个考虑因素是:

您在基类中没有实现,只有子类必须定义自己的实现。您需要抽象类而不是接口,因为您想与子类共享状态。

抽象类在相关类之间建立“是”关系,接口在不相关类之间提供“具有”功能


关于你的问题,其中适用于大多数编程语言包括Java之前的第二部分的java-8版本

一如既往地需要权衡,接口为您提供了关于基类的自由,抽象类为您提供了以后添加新方法的自由。–埃里希·伽玛(Erich Gamma)

您无需更改代码中的许多其他内容就无法更改接口

如果您希望抽象类更早地基于以上两个考虑进行接口,则必须重新考虑一下,因为默认方法为接口添加了强大的功能。

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

要在接口和抽象类之间选择它们之一,oracle文档页面引用:

抽象类类似于接口。您无法实例化它们,它们可能包含使用或不使用实现声明的方法的混合。但是,使用抽象类,您可以声明非静态和最终字段,并定义公共,受保护的和私有的具体方法。

使用接口时,所有字段都自动是公共的,静态的和最终的,并且您声明或定义的所有方法(作为默认方法)都是公共的。此外,无论是否抽象,您都只能扩展一个类,而您可以实现任意数量的接口。

有关更多详细信息,请参阅这些相关问题:

接口vs抽象类(通用OO)

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

总结:现在的平衡越来越倾向于接口

除了上面提到的情况以外,还有其他任何情况下,我们特别需要使用抽象类(可以看到模板方法设计模式仅在概念上基于此)?

除模板方法模式外,某些设计模式还使用抽象类(通过接口)。

创作模式:

抽象工厂模式

结构模式:

Decorator_pattern

行为模式:

Mediator_pattern


这是:“抽象类在相关类之间建立了“是”关系,而接口在不相关类之间提供了“具有”功能。
加百利

3

你说的不对。有很多方案。不可能将其简化为单个8字规则。


1
除非你模棱两可 尽可能使用界面;)
Peter Lawrey 2012年

@PeterLawrey是的,不要让循环论点使您慢下来;-)
Lorne侯爵,2012年

毕竟这是“堆栈溢出”。;)我的意思是,如果可以使用更简单的界面,请这样做。否则,您别无选择,只能使用抽象类。我认为这不是很复杂。
彼得·劳瑞

我认为你可以提供一个更具建设性的想法。喜欢谈论一些有代表性的场景/
Adams.H 2014年

3

最简单的答案是,当您已经在其中实现了某些所需的功能时,扩展抽象类。

如果实现接口,则必须实现所有方法。但是对于抽象类而言,您需要实现的方法数量可能会更少。

模板设计模式中,必须定义一个行为。此行为取决于抽象的其他方法。通过创建子类并定义这些方法,您实际上可以定义主要行为。基本行为不能出现在接口中,因为接口只是声明而未定义任何内容。因此,模板设计模式始终带有抽象类。如果要保持行为的完整性,则必须扩展抽象类,但不要覆盖主要行为。


有关纯虚函数的其他参考,将添加有关抽象类和接口的收敛的更多见解, Pure virtual functions can also be used where the method declarations are being used to define an interface - similar to what the interface keyword in Java explicitly specifies. In such a use, derived classes will supply all implementations. In such a design pattern, the abstract class which serves as an interface will contain only pure virtual functions, but no data members or ordinary methods. 第(1/2)部分
Abhijeet 2015年

抽象类和接口的第(2/2)部分no data members or ordinary methods在上面的[抽象类]中的最后一行中进行了解释。
阿披耶特(Abhijeet)2015年

3

我认为,基本的区别在于an interface can't contain non abstract methods while an abstract class can。因此,如果子类具有共同的行为,则此行​​为可以在超类中实现,并因此在子类中继承

我还引用了“ java的软件体系结构设计模式”一书中的以下内容

“在Java编程语言中,不支持多重继承。这意味着一个类只能从一个类继承。因此,仅在绝对必要时才应使用继承。应尽可能在以下位置声明表示共同行为的方法: Java接口的形式,可以由不同的实现程序类实现,但是接口受到不能提供方法实现的限制,这意味着每个接口的实现程序都必须显式实现接口中声明的所有方法,即使其中一些方法方法表示功能的不变部分,并且在所有实现者类中的实现都完全相同,这导致了冗余代码。下面的示例演示了在这种情况下如何使用Abstract Parent Class模式,而无需冗余的方法实现。”


2

抽象类在两个重要方面与接口不同

  • 它们为所选方法提供了默认实现(您的答案已涵盖)
  • 抽象类可以具有状态(实例变量)-因此,这是您要使用它们代替接口的另一种情况

我会说完接口可以有变量,但是默认情况下它们是final。
Tomasz Mularczyk '16

1

这是一个很好的问题,这两者之间并不相似,但出于某些相同的原因可以使用,例如重写。创建时最好使用Interface。归根结底,这对调试很有用。


0

Abstract classes should be extended when you want to some common behavior to get extended。Abstract超类将具有公共行为,并将定义子类应实现的抽象方法/特定行为。

Interfaces allows you to change the implementation anytime allowing the interface to be intact


0

这是我的理解,希望对您有所帮助

抽象类:

  1. 可以具有继承的成员变量(不能在接口中完成)
  2. 可以有构造函数(接口不能)
  3. 它的方法可以具有任何可见性(即:私有,受保护等,而所有接口方法都是公共的)
  4. 可以有已定义的方法(实现方法)

接口:

  1. 可以有变量,但它们都是公共静态最终变量
    • 不会在静态范围内变化的常量值
    • 非静态变量需要一个实例,并且您无法实例化接口
  2. 所有方法都是抽象的(抽象方法中没有代码)
    • 所有代码都必须实际编写在实现特定接口的类中

0

抽象和接口的用法:

一个拥有“存在关系”,另一个拥有“存在关系”

默认属性已抽象设置,其他属性可通过接口表示。

例如:->在人类中,我们具有一些默认属性,例如吃饭,睡觉等。但是,如果有人有任何其他课程活动,例如游泳,玩耍等,则可以通过Interface来表达。

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.