接口和抽象类之间到底有什么区别?
当前回答
让我们再次讨论这个问题:
首先要告诉你的是,1/1和1*1的结果是相同的,但这并不意味着乘法和除法是相同的。显然,他们有着良好的关系,但请注意,你们两个都不同。
我将指出主要的区别,其余的已经解释过了:
抽象类对于建模类层次结构非常有用。乍一看任何需求,我们都部分清楚要构建什么,但我们知道要构建什么。所以抽象类就是基类。
接口对于让其他层次结构或类知道我能做什么很有用。当你说我有能力做某事时,你必须有这种能力。接口将标记类必须实现相同的功能。
其他回答
抽象类与接口的主题主要是语义。
抽象类在不同的编程语言中通常充当接口的超集,除了一点,即可以实现多个接口,但只能继承一个类。
接口定义了某件事情必须能够做的事情;类似于合同,但不提供其实现。
抽象类定义了什么是什么,它通常托管子类之间的共享代码。
例如,格式化程序应该能够格式化()某些内容。描述这种情况的常见语义是创建一个接口IFormatter,该接口带有一个format()声明,其作用类似于一个契约。但是IFormatter并不描述什么是什么,而是描述它应该能够做什么。在本例中,我们创建一个抽象类。。。因此,我们创建了一个实现接口的抽象类Formatter。这是一个非常描述性的代码,因为我们现在知道我们有一个格式化程序,我们现在知道每个格式化程序必须能够做什么。
还有一个非常重要的主题是文档(至少对某些人来说…)。在您的文档中,您可能希望在子类中解释Formatter实际上是什么。有一个抽象类Formatter非常方便,您可以链接到子类中的文档。这是非常方便和通用的。另一方面,如果您没有抽象类Formatter,而只有接口IFormatter,则必须在每个子类中解释Formatter实际上是什么,因为接口是一个契约,你不会在接口的文档中描述Formatter实际上是什么-至少这不是常见的事情,你会打破大多数开发人员认为正确的语义。
注意:使抽象类实现接口是一种非常常见的模式。
总结起来最简单的方法是:
完全抽象,除了默认和静态方法;虽然它有默认和静态方法的定义(方法签名+实现),但它只有其他方法的声明(方法签名)。受比类更宽松的规则约束(类可以实现多个接口,接口可以从多个接口继承)。所有变量都是隐式常量,无论是否指定为公共静态final。所有成员都是隐式公共的,无论是否指定为公共成员。通常用于保证实现类将具有指定的特性和/或与实现相同接口的任何其他类兼容。
同时,抽象类是:
从完全抽象到完全实现,具有一个或多个抽象方法的倾向。可以包含声明和定义,声明标记为抽象。一个完整的类,并且受制于管理其他类的规则(只能从一个类继承),条件是它不能实例化(因为不能保证它完全实现)。可以具有非常量成员变量。可以实现成员访问控制,将成员限制为受保护、私有或私有包(未指定)。通常用于提供可由多个子类共享的尽可能多的实现,或者提供程序员能够提供的尽可能的实现。
或者,如果我们想将其归结为一句话:接口是实现类所拥有的,而抽象类是子类所拥有。
许多初级开发人员错误地将接口、抽象类和具体类视为同一事物的细微变化,并纯粹基于技术原因选择其中之一:我需要多重继承吗?我需要一些地方来放置常用方法吗?除了一堂具体的课,我还需要为别的事情而烦恼吗?这是错误的,隐藏在这些问题中的是主要问题:“我”。当你自己编写代码时,你很少想到其他现在或将来的开发人员正在处理或使用你的代码。
接口和抽象类,虽然从技术角度看很相似,但有着完全不同的含义和目的。
总结
接口定义了某个实现将为您实现的契约。抽象类提供了您的实现可以重用的默认行为。
备选摘要
接口用于定义公共API抽象类用于内部使用和定义SPIs
隐藏实现细节的重要性
具体的类以非常具体的方式完成实际工作。例如,ArrayList使用一个连续的内存区域以紧凑的方式存储对象列表,这提供了快速的随机访问、迭代和就地更改,但在插入、删除和偶尔甚至添加时非常糟糕;同时,LinkedList使用双链接节点来存储对象列表,这提供了快速迭代、就地更改和插入/删除/添加,但在随机访问时非常糟糕。这两种列表针对不同的用例进行了优化,如何使用它们非常重要。当您试图从与您密切交互的列表中挤出性能时,当选择列表类型时,您应该仔细选择要实例化的列表。
另一方面,列表的高级用户并不真正关心它是如何实现的,他们应该与这些细节隔离开来。让我们想象一下,Java没有公开List接口,但只有一个具体的List类,这实际上就是LinkedList现在的样子。所有Java开发人员都会定制自己的代码以适应实现细节:避免随机访问,添加缓存以加快访问速度,或者自己重新实现ArrayList,尽管这与所有其他仅适用于List的代码不兼容。那太可怕了。。。但现在想象一下,Java主控实际上意识到链接列表对于大多数实际用例来说都是可怕的,并决定切换到数组列表,以获得唯一可用的list类。这将影响世界上每一个Java程序的性能,人们对此不会感到高兴。主要原因是实现细节是可用的,而开发人员认为这些细节是他们可以依赖的永久契约。这就是为什么隐藏实现细节,只定义抽象契约很重要。这就是接口的目的:定义一个方法接受什么样的输入,以及期望什么样的输出,而不暴露所有可能诱使程序员调整代码以适应未来任何更新可能改变的内部细节的勇气。
抽象类位于接口和具体类之间。它应该帮助实现共享公共或无聊的代码。例如,AbstractCollection提供了基于大小为0的isEmpty的基本实现,包含作为迭代和比较,addAll作为重复添加等等。这让实现专注于区分它们的关键部分:如何实际存储和检索数据。
API与SPI
接口是代码不同部分之间的低内聚网关。它们允许图书馆存在和发展,而不会在内部发生变化时破坏每个图书馆用户。它叫做应用程序编程接口,而不是应用程序编程类。在较小的规模上,它们还允许多个开发人员在大型项目上成功协作,通过文档化的界面将不同的模块分离。
抽象类是在实现接口时使用的高内聚性帮助程序,假定实现细节级别。或者,抽象类用于定义SPI、服务提供商接口。
API和SPI之间的区别很细微,但很重要:对于API,重点是谁使用它,而对于SPI,重点是由谁实现它。
向API添加方法很容易,API的所有现有用户仍将编译。向SPI添加方法很难,因为每个服务提供商(具体实现)都必须实现新方法。如果使用接口定义SPI,则每当SPI合约发生变化时,提供商都必须发布新版本。如果改用抽象类,则可以根据现有的抽象方法定义新方法,或者将新方法定义为空抛出未实现的异常存根,这至少允许旧版本的服务实现仍然编译和运行。
关于Java 8和默认方法的说明
虽然Java8引入了接口的默认方法,这使得接口和抽象类之间的界限更加模糊,但这并不是为了实现可以重用代码,而是为了更容易地更改同时用作API和SPI的接口(或者错误地用于定义SPI而不是抽象类)。
使用哪一个?
这个东西应该由代码的其他部分或其他外部代码公开使用吗?向其添加一个接口,以从公共抽象契约中隐藏实现细节,这是该事物的一般行为。这件事是不是应该有多个实现,有很多共同的代码?既要做一个接口,又要做一个抽象的、不完整的实现。是否只有一个实现,而没有其他人会使用它?让它成为一个具体的课程。“ever”是一个很长的时间,你可以安全地使用它,并在它上面添加一个界面。
一个推论:另一种方法往往是错误的:当使用一个东西时,总是尝试使用您实际需要的最通用的类/接口。换句话说,不要将变量声明为ArrayListtheList=newArrayList(),除非您实际上对它是一个数组列表有很强的依赖性,并且没有其他类型的列表可以为您删除它。如果它是一个列表,而不是任何其他类型的集合这一事实实际上无关紧要,请改用ListtheList=newArrayList,甚至使用CollectiontheCollection=newArrayNist。
接口
接口是一个契约:编写接口的人说,“嘿,我接受这样的东西”,而使用接口的人则说“好的,我编写的类是这样的”。
接口是一个空壳。只有方法的签名,这意味着方法没有主体。接口无法执行任何操作。这只是一种模式。
例如(伪代码):
// I say all motor vehicles should look like this:
interface MotorVehicle
{
void run();
int getFuel();
}
// My team mate complies and writes vehicle looking that way
class Car implements MotorVehicle
{
int fuel;
void run()
{
print("Wrroooooooom");
}
int getFuel()
{
return this.fuel;
}
}
实现一个接口只消耗很少的CPU,因为它不是一个类,只是一堆名称,因此不需要进行任何昂贵的查找。在重要的时候,比如在嵌入式设备中,这是非常棒的。
抽象类
与接口不同,抽象类是类。它们的使用成本更高,因为从它们继承时需要进行查找。
抽象类看起来很像接口,但它们有更多:您可以为它们定义行为。这更像是一个人说,“这些课程应该看起来像那样,而且它们有共同点,所以填空吧!”。
例如:
// I say all motor vehicles should look like this:
abstract class MotorVehicle
{
int fuel;
// They ALL have fuel, so lets implement this for everybody.
int getFuel()
{
return this.fuel;
}
// That can be very different, force them to provide their
// own implementation.
abstract void run();
}
// My teammate complies and writes vehicle looking that way
class Car extends MotorVehicle
{
void run()
{
print("Wrroooooooom");
}
}
实施
虽然抽象类和接口被认为是不同的概念,但实现有时会使该语句不真实。有时,他们甚至不是你想象的那样。
在Java中,这个规则是强制执行的,而在PHP中,接口是没有声明方法的抽象类。
在Python中,抽象类更多地是一种编程技巧,您可以从ABC模块中获得,并且实际上使用元类,因此使用类。接口与这种语言中的duck类型更为相关,它混合了约定和调用描述符的特殊方法(__method__方法)。
与编程一样,还有另一种语言的理论、实践和实践:-)
要点是:
抽象是面向对象的。它提供了一个“对象”应该具有的基本数据和/或它应该能够执行的功能。它关注对象的基本特性:它具有什么和它可以做什么。因此,从同一抽象类继承的对象共享基本特性(泛化)。接口是面向功能的。它定义了对象应该具有的功能。不管它是什么对象,只要它可以执行界面中定义的这些功能,就可以了。它忽略了其他一切。一个对象/类可以包含几个(组)功能;因此,类可以实现多个接口。