Java中的抽象类与接口

时间:2012-04-06 06:40:59

标签: java design-patterns interface abstract-class

我被问到一个问题,我想在这里查看我的答案。

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

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

我说错了吗?

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

1)当需求需要在特定操作的每个子类中实现相同的功能(实现方法)和其他一些操作的不同功能(仅方法签名)时,使用抽象类

2)如果您需要将签名设置为相同(并且实现不同),请使用接口,以便您可以遵守接口实现

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

重申一下这个问题:除了上面提到的那些之外,还有其他任何场景,具体我们需要使用抽象类(一个是模板方法设计模式在概念上仅基于此)吗?

接口与抽象类

在这两者之间做出选择真的取决于你想做什么,但幸运的是,Erich Gamma可以帮助我们。

一如既往地存在权衡,界面为您提供了基类的自由,抽象类为您提供了以后添加新方法的自由。 - Erich Gamma

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

Abstract classes应主要用于密切相关的对象。 Interfaces更善于为不相关的类提供通用功能。

16 个答案:

答案 0 :(得分:75)

何时使用界面

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

何时使用抽象类

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

何时使用

您可以提供两全其美,界面和抽象类。如果他们选择,实现者可以忽略您的抽象类。这样做的唯一缺点是通过其接口名称调用方法比通过其抽象类名称调用方法稍慢。

答案 1 :(得分:27)

  

重申这个问题:除此之外还有其他任何情况   上面提到的具体我们需要使用抽象类   (一个是模板方法设计模式是概念上的基础   这只是)

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

来自个人博客post

<强>接口

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

    抽象类:

    1. 一个类最多可以扩展一个抽象类
    2. 抽象类可以包含代码
    3. 抽象类可以定义静态和实例常量(最终)
    4. 抽象类可以定义实例变量
    5. 修改现有的抽象类代码会对扩展类产生连锁反应(实现维护)
    6. 向抽象类添加新方法对扩展类没有连锁反应
    7. 抽象类可以实现接口
    8. 抽象类可以实现私有和受保护的方法
    9. 抽象类应该用于(部分)实现。它们可能是限制API合同实施方式的一种手段。

答案 2 :(得分:11)

如果您拥有所有类具有相同结构但完全具有不同功能的场景,则使用接口。

当您拥有所有类具有相同结构但具有相同功能和不同功能的场景时,将使用抽象类。

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

答案 3 :(得分:4)

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

如果这些语句中的任何一个适用于您的场景,请考虑使用抽象类:

您希望在几个密切相关的类之间共享代码。

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

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

如果这些陈述中的任何一个适用于您的情况,请考虑使用接口:

您希望不相关的类能够实现您的界面。 例如,Comparable和Cloneable接口由许多不相关的类实现。

您希望指定特定数据类型的行为,但不关心谁实现其行为。

您希望利用类型的多重继承。

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

答案 4 :(得分:4)

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

来自界面上的oracle文档page

  

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

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

更喜欢抽象类而不是接口:

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

抽象类建立&#34;是&#34;相关类和接口之间的关系提供了&#34;有一个&#34;不相关类之间的能力

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

  

一如既往的权衡,界面为您提供了基类的自由,抽象类让您可以自由地在以后添加新方法。 - Erich Gamma

     

您无法更改界面而无需更改代码中的许多其他内容

如果您更喜欢抽象类与之前的两个注意事项接口,那么您必须重新思考,因为default methods已经为接口添加了强大的功能。

  

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

要在接口和抽象类之间选择其中一个,oracle文档page引用:

  

抽象类与接口类似。您无法实例化它们,并且它们可能包含使用或不使用实现声明的混合方法。但是,对于抽象类,您可以声明非静态和最终的字段,并定义public,protected和private具体方法。

     

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

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

Interface vs Abstract Class (general OO)

How should I have explained the difference between an Interface and an Abstract class?

总结: 现在,余额更倾向于接口

  

除了上面提到的那些之外,还有其他任何场景,具体我们需要使用抽象类(一个是模板方法设计模式在概念上仅基于此)吗?

除了Template方法模式之外,一些设计模式使用抽象类(在接口上)。

创作模式:

Abstract_factory_pattern

结构模式:

Decorator_pattern

行为模式:

Mediator_pattern

答案 5 :(得分:3)

你不正确。有很多场景。只是不可能将它简化为单个8字规则。

答案 6 :(得分:3)

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

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

template design pattern中必须定义一个行为。此行为取决于其他抽象方法。通过创建子类并定义这些方法,您实际上定义了主要行为。底层行为不能在接口中,因为接口没有定义任何东西,它只是声明。因此模板设计模式总是带有抽象类。如果要保持行为的流程不变,则必须扩展抽象类,但不要覆盖主要行为。

答案 7 :(得分:3)

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

我还引用了以下“软件架构设计ppatterns in java”一书

“在Java编程语言中,不支持多重继承。 这意味着一个类只能从一个类继承。因此继承 只有在绝对必要时才应该使用。尽可能的方法 表示公共行为应该以Java接口的形式声明 由不同的实施者类实施。但接口受到影响 他们无法提供方法实现的限制。这意味着 接口的每个实现者必须显式实现声明的所有方法 在界面中,即使其中一些方法代表了不变的部分 在所有实现者类中具有完全相同的功能。这导致冗余代码。以下示例演示 如何在没有这种情况下使用抽象父类模式 需要冗余的方法实现。“

答案 8 :(得分:2)

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

  • 它们为所选方法提供默认实现(答案涵盖的内容)
  • 抽象类可以有状态(实例变量) - 所以这是你想用它们代替接口的另一种情况

答案 9 :(得分:2)

这里有很多很棒的答案,但我经常发现使用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);

}

现在,在销售部门和其他业务部门工作的工程师可以开始界面与您的系统一起向现有应用添加订单放置功能。而你甚至还没有开始建设!这是界面的力量。

所以你继续为 stock 交易者建立系统;他们听说你的系统有功能找到便宜的股票,非常渴望尝试一下!您可以在名为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);
    }

但现在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);
    }

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

注意:有人可能想知道为什么findGreatDeals()不是界面的一部分。请记住,界面定义了交易系统的最常见组件。另一位工程师可能会开发一个完全不同的交易系统,他们并不关心如何找到好的交易。界面保证了销售台也可以与他们的系统连接,因此最好不要将界面与应用程序概念纠缠在一起,例如&#34;很棒的交易&#34;。

答案 10 :(得分:1)

这是一个很好的问题其中两个并不相似,但可以出于某些相同的原因使用,例如重写。创建时最好使用Interface。当它归结为课程时,它适用于调试。

答案 11 :(得分: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

答案 12 :(得分:0)

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

抽象类:

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

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

答案 13 :(得分:0)

抽象和界面的用法:

一个人有“Is-A-Relationship”,另一个人有“Has-A-Relationship”

默认属性已设置为abstract,其他属性可通过interface表示。

示例: - &gt;在人类中,我们有一些正在吃饭,睡觉等的默认属性,但如果有人有任何其他课程活动,如游泳,玩耍等,可以通过界面表达。

答案 14 :(得分:0)

抽象类与接口之间的区别

抽象类

  • 抽象类可以具有抽象和非抽象方法
  • 抽象类可能包含非最终变量
  • 抽象类可以具有最终,非最终,静态和非静态变量
  • 抽象类可以提供接口的实现
  • 可以使用关键字“ extends”扩展抽象类
  • 抽象类可以扩展另一个Java类并实现多个Java接口
  • Java抽象类可以具有私有,受保护等类成员

接口

  • 接口只能有抽象方法
  • 在Java接口中声明的变量默认为final
  • 接口只有静态变量和最终变量
  • 接口无法提供抽象类的实现
  • 可以使用关键字“ implements”实现Java接口
  • 一个接口只能扩展另一个Java接口
  • 默认情况下,Java接口的成员是公开的

答案 15 :(得分:0)

我认为这里的答案没有抓住要点:

Java 接口(问题是关于 Java,但在其他语言中也有类似的机制)是一种部分支持多重继承的方式,即 method-only 继承。

它类似于 PHP 的 traits 或 Python 的鸭子输入

除此之外,没有任何额外的东西是您真正需要的接口——并且您无法实例化 Java 接口。