Java中的抽象类与接口

我被问了一个问题,我想在这里回答我的答案。

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

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

我对么 ?

如果我无法清楚地说出问题,我感到抱歉。
我知道抽象类和接口的基本区别。

1)当需求是这样的时候,使用抽象类:我们需要在每个子类中为特定的操作(实现该方法)实现相同的function,并且为其他一些操作(只有方法签名)

2)使用接口,如果你需要把签名放在同一个(和实现不同),以便你可以遵守接口的实现

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

重申一个问题:除了上面提到的那些,还有没有其他的场景需要使用抽象类(一种是模板方法devise模式只是基于这个概念)?

接口与抽象类

在这两者之间进行select,取决于你想要做什么,但对我们来说幸运的是,Erich Gamma可以帮助我们一点。

和往常一样有一个权衡,一个接口给你基础类的自由 ,一个抽象类给你随后添加新方法自由 。 – Erich Gamma

不能去改变一个接口,而不必在你的代码中改变很多其他的东西 ,所以唯一的方法就是创build一个全新的接口,这可能不是一件好事。

Abstract classes应主要用于密切相关的对象。 Interfaces更好地为不相关的类提供通用的function。

何时使用接口

一个接口允许有人从头开始实现你的接口,或者在其他一些代码中实现你的接口,这些代码的原始或主要目的与你的接口完全不同。 对他们来说,你的界面只是偶然的,必须添加到他们的代码才能使用你的包。 缺点是界面中的每个方法都必须公开。 你可能不想暴露一切。

何时使用抽象类

相比之下,抽象类提供了更多的结构。 它通常定义了一些默认的实现,并提供了一些对于完整实现有用的工具。 问题是,使用它的代码必须使用你的类作为基础。 如果其他希望使用你的包的程序员已经独立开发了他们自己的类层次结构,这可能是非常不方便的。 在Java中,一个类只能从一个基类inheritance。

何时使用两者

你可以提供两个世界中最好的,一个接口和一个抽象类。 如果他们select,实现者可以忽略你的抽象类。 这样做的唯一缺点是通过接口名称调用方法比通过抽象类名称调用方法稍慢。

重申这个问题:除了上面提到的这些情况之外,还有其他一些情况,特别是我们需要使用抽象类(一个是模板方法devise模式在概念上只是基于这个)

是的,如果你使用JAXB。 它不喜欢接口。 你应该使用抽象类或者用generics来解决这个限制。

从个人博客:

接口:

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

一般来说,接口应该用来定义合同(要实现的是什么,而不是如何实现)。

抽象类:

  1. 一个类最多可以扩展一个抽象类
  2. 抽象类可以包含代码
  3. 抽象类可以定义静态常量和实例常量(最终)
  4. 抽象类可以定义实例variables
  5. 现有抽象类代码的修改对扩展类有影响(实施维护)
  6. 向抽象类添加新方法对扩展类没有影响
  7. 抽象类可以实现一个接口
  8. 抽象类可以实现私有和受保护的方法

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

当你有所有类具有相同结构但完全具有不同function的场景时使用接口。

抽象类用于当所有类具有相同结构但具有相同和不同function的场景时。

看看文章: http : //shoaibmk.blogspot.com/2011/09/abstract-class-is-class-which-cannot-be.html

你不正确 有很多情况。 它是不可能的减less到一个单一的8字规则。

在我看来,基本的区别是an interface can't contain non abstract methods while an abstract class can 。 所以如果子类共享一个共同的行为,这个行为可以在超类中实现,因此在子类中inheritance

另外我引用了“java中的软件架构deviseppatterns”一书

“在Java编程语言中,不支持多inheritance,这意味着一个类只能从一个类inheritance,因此inheritance应该只在绝对必要时才使用,只要有可能,表示共同行为的方法应该在Java接口的forms由不同的实现者类来实现,但是接口的缺点是不能提供方法的实现,这意味着每个接口的实现者都必须明确地实现接口中声明的所有方法,即使这些方法中的一些方法代表了function的不变部分,并且在所有实现类中具有完全相同的实现,从而导致了冗余代码的下面的例子演示了如何在这种情况下使用Abstract Parent Class模式,而不需要冗余的方法实现。

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

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

你想在几个密切相关的类中分享代码。

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

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

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

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

您想要指定特定数据types的行为,但不关心谁实现其行为。

你想利用types的多重inheritance。

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

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

  • 它们提供了所选方法的默认实现(这是由您的答案涵盖)
  • 抽象类可以有状态(实例variables) – 所以这是一个更多的情况,你想用它们来代替接口

最简单的答案是,当你所寻求的一些function已经被实现时, 扩展抽象类。

如果你实现了接口,你必须实现所有的方法。 但是对于抽象类来说,你需要实现的方法可能更less。

在模板devise模式中 ,必须定义一个行为。 这种行为取决于其他抽象的方法。 通过创build子类并定义这些方法,您实际上定义了主要的行为。 底层行为不能在接口中,因为接口没有定义任何东西,它只是声明。 所以一个模板devise模式总是带有一个抽象类。 如果要保持行为的stream畅性,则必须扩展抽象类,但不要覆盖主要行为。

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

您是一家投资银行的软件开发人员,需要build立一个系统,将订单input市场。 您的界面捕捉了交易系统的最普遍的概念,

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

并可以在ITradeSystem的界面中ITradeSystem

 public interface ITradeSystem{ public void placeOrder(IOrder order); public void ackOrder(IOrder order); } 

现在,在销售部门和其他业务部门工作的工程师可以开始与您的系统进行交互 ,为其现有的应用程序添加下单function。 而且你还没有开始build设呢! 这是接口的力量。

所以,你要为股票交易者build立系统。 他们已经听说你的系统有一个function,find便宜的股票,并非常渴望尝试! 你用一个名为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()是所有交易者实际上关心的。

现在这里是抽象类进入的地方。 你的老板通知你,货币交易者也想使用你的系统。 看看外汇市场,你会发现pipe道几乎和股票市场一模一样。 事实上, 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); } 

但是现在FX专家可以通过简单地为货币提供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); } 

编程接口function强大,但类似的应用程序通常以几乎相同的方式重新实现方法。 使用抽象类避免了重新展示,同时保留了界面的力量。

注意:有人可能会奇怪为什么findGreatDeals()不是接口的一部分。 请记住,界面定义了交易系统的最一般的组成部分。 另一位工程师可能会开发一个完全不同的交易系统,他们不关心寻找好交易。 界面保证了销售台也可以连接到他们的系统,所以最好不要把你的界面和“伟大的交易”这样的应用概念混为一谈。

这是一个很好的问题这两个是不相似的,但可以使用一些相同的原因,如重写。 创build时最好使用Interface。 当归类时,对于debugging是有好处的。

在过去的三年中,事情已经发生了很大的变化,增加了与Java 8版本接口的新function。

从界面上的oracle文档页面 :

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

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

还有一个考虑更喜欢抽象类比接口:

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

抽象类build立相关类与接口之间的“是”关系,为不相关类之间提供“有”能力


关于你的问题的第二部分,这对大多数编程语言(包括java-8发布之前的java)都是有效的

和往常一样有一个权衡,一个接口给你基础类的自由,一个抽象类给你随后添加新方法的自由。 – 埃里克伽玛

你不能去改变一个接口,而不必在你的代码中改变很多其他的东西

如果你更喜欢抽象类来接口以上两点的考虑,你现在必须重新思考,因为默认方法增加了强大的接口能力。

使用默认方法,可以将新function添加到库的接口,并确保与为这些接口的旧版本编写的代码的二进制兼容性。

要在界面和抽象类之间select其中的一个,oracle文档页面引用:

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

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

详情请参阅以下相关问题:

界面与抽象类(通用OO)

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

总而言之: 现在平衡更倾向于接口

除了上面提到的,还有什么其他场景,特别是我们需要使用抽象类(一个是看模板方法devise模式是在概念上是基于这个)?

一些devise模式除了使用Template方法模式外,还使用抽象类(通过接口)。

创造型模式:

Abstract_factory_pattern

结构模式:

Decorator_pattern

行为模式:

Mediator_pattern

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

这是我的理解,希望这有助于

抽象类:

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

接口:

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

抽象和接口的使用:

一个是“是一种关系”,另一个是“有一种关系”

默认属性已经在抽象中设置,额外的属性可以通过接口来表示。

例如: – >在人类中,我们有一些默认属性是吃饭,睡觉等等,但是如果有人有任何其他课程活动,如游泳,玩耍等,那么可以用界面来expression。