Thi*_*ker 456 java oop inheritance abstract-class interface
在我的一次访谈中,我被要求解释Interface和Abstract类之间的区别.
这是我的回答:
Java接口的方法是隐式抽象的,不能有实现.Java抽象类可以具有实现默认行为的实例方法.
在Java接口中声明的变量默认为final.抽象类可能包含非最终变量.
默认情况下,Java接口的成员是公共的.Java抽象类可以具有类似私有,受保护等类通常的类成员.
应使用关键字"implements"实现Java接口; 应使用关键字"extends"扩展Java抽象类.
接口只能扩展另一个Java接口,抽象类可以扩展另一个Java类并实现多个Java接口.
Java类可以实现多个接口,但它只能扩展一个抽象类.
然而,面试官并不满意,并告诉我这个描述代表了" 书本知识 ".
他告诉我一个更实际的回答,解释我何时会使用实际例子在界面上选择一个抽象类.
我哪里做错了?
Vim*_*era 497
我先给你举个例子:
public interface LoginAuth{
public String encryptPassword(String pass);
public void checkDBforUser();
}
Run Code Online (Sandbox Code Playgroud)
现在假设您的应用程序中有3个数据库.然后,该数据库的每个实现都需要定义上述两种方法:
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
}
Run Code Online (Sandbox Code Playgroud)
但是如果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();
}
Run Code Online (Sandbox Code Playgroud)
现在在每个子类中,我们只需要实现一个方法 - 依赖于数据库的方法.
我尽我所能,希望这会清除你的疑虑.
Sha*_*ena 199
在这个世界上没有什么是完美的.他们可能期待更多实用的方法.
但是在您的解释之后,您可以使用稍微不同的方法添加这些行.
接口是规则(规则,因为您必须为它们提供一个您不能忽略或避免的实现,以便它们像规则一样强制执行),这些规则在软件开发中的各个团队之间起着共同的理解文档的作用.
接口提供了想要做什么但不知道如何做的想法.因此,实现完全依赖于开发人员遵循给定的规则(意味着给定方法的签名).
抽象类可以包含抽象声明,具体实现或两者.
抽象声明就像要遵循的规则,具体实现就像指南(你可以按原样使用它,或者你可以通过覆盖并给它自己的实现来忽略它).
此外,具有相同签名的哪些方法可以改变不同上下文中的行为作为接口声明被提供作为在不同上下文中相应地实现的规则.
编辑: Java 8有助于在接口中定义默认和静态方法.
public interface SomeInterfaceOne {
void usualAbstractMethod(String inputString);
default void defaultMethod(String inputString){
System.out.println("Inside SomeInterfaceOne defaultMethod::"+inputString);
}
}
Run Code Online (Sandbox Code Playgroud)
现在当一个类实现SomeInterface时,并不是必须为接口的默认方法提供实现.
如果我们有另外一个接口,有以下方法:
public interface SomeInterfaceTwo {
void usualAbstractMethod(String inputString);
default void defaultMethod(String inputString){
System.out.println("Inside SomeInterfaceTwo defaultMethod::"+inputString);
}
}
Run Code Online (Sandbox Code Playgroud)
Java不允许扩展多个类,因为它导致"钻石问题",其中编译器无法决定使用哪个超类方法.使用默认方法,接口也会出现菱形问题.因为如果一个类正在实现两者
SomeInterfaceOne and SomeInterfaceTwo
Run Code Online (Sandbox Code Playgroud)
并且没有实现常见的默认方法,编译器无法决定选择哪一个.为了避免这个问题,在java 8中必须实现不同接口的通用默认方法.如果任何类正在实现上述接口,则必须为defaultMethod()方法提供实现,否则编译器将抛出编译时错误.
Dan*_*rps 161
您对使用和实施方面的实际差异做了很好的总结,但没有说明意义上的差异.
一个接口是行为实现的类都会有一个描述.实现类确保它将具有可以在其上使用的这些方法.它基本上是一个合同或一个班级必须做出的承诺.
一个抽象类是共享行为,不需要重复创建不同的子类的基础.子类必须完成该行为并具有覆盖预定义行为的选项(只要它未定义为final
或private
).
你会在java.util
包中找到很好的例子,包括接口List
和类似AbstractList
已经实现接口的抽象类.在官方文档描述AbstractList
如下:
此类提供List接口的骨干实现,以最大限度地减少实现由"随机访问"数据存储(例如数组)支持的此接口所需的工作量.
小智 84
接口由单例变量(public static 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...
}
}
Run Code Online (Sandbox Code Playgroud)
在上面的示例中,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.
}
}
Run Code Online (Sandbox Code Playgroud)
如果我们将来向给定的抽象类添加方法(具体/抽象),那么实现类将不需要更改其代码.但是,如果我们将来在接口中添加方法,我们必须向实现该接口的所有类添加实现,否则会发生编译时错误.
还有其他差异,但这些是主要的,可能是你的面试官所期望的.希望这很有帮助.
Sky*_*ker 44
Java 8接口更改包括接口中的静态方法和默认方法.在Java 8之前,我们可以在接口中只有方法声明.但是从Java 8开始,我们可以在接口中使用默认方法和静态方法.
在引入Default Method之后,接口和抽象类似乎是相同的.但是,它们在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");
}
}
Run Code Online (Sandbox Code Playgroud)
以下类将在Java JDK 8中成功编译,
public class OldInterfaceImpl implements OldInterface {
public void existingMethod() {
// existing implementation is here…
}
}
Run Code Online (Sandbox Code Playgroud)
如果您创建OldInterfaceImpl的实例:
OldInterfaceImpl obj = new OldInterfaceImpl ();
// print “New default method add in interface”
obj.newDefaultMethod();
Run Code Online (Sandbox Code Playgroud)
默认方法永远不会是最终的,无法同步,也不能覆盖Object的方法.它们总是公开的,严重限制了编写简短和可重用方法的能力.
可以向接口提供缺省方法,而不会影响实现类,因为它包含实现.如果在使用实现定义的接口中添加了每个方法,则不会影响实现类.实现类可以覆盖接口提供的默认实现.
默认方法允许向现有接口添加新功能,而不会破坏这些接口的旧实现.
当我们扩展包含默认方法的接口时,我们可以执行以下操作,
对于Java 8,JDK集合已经扩展,并且forEach方法被添加到整个集合(与lambdas一起使用).使用传统方式,代码如下所示,
public interface Iterable<T> {
public void forEach(Consumer<? super T> consumer);
}
Run Code Online (Sandbox Code Playgroud)
由于此结果每个实现具有编译错误的类,因此添加了必需实现的默认方法,以便不应更改现有实现.
使用Default方法的Iterable接口如下,
public interface Iterable<T> {
public default void forEach(Consumer
<? super T> consumer) {
for (T t : this) {
consumer.accept(t);
}
}
}
Run Code Online (Sandbox Code Playgroud)
已经使用相同的机制在JDK接口中添加Stream而不破坏实现类.
由于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 {
}
Run Code Online (Sandbox Code Playgroud)
上面的代码将无法编译,出现以下错误,
java:class Impl从InterfaceA和InterfaceB类型继承defaultMethod()的无关默认值
为了修复这个类,我们需要提供默认方法实现:
public class Impl implements InterfaceA, InterfaceB {
public void defaultMethod(){
}
}
Run Code Online (Sandbox Code Playgroud)
此外,如果我们想要调用任何超级接口而不是我们自己的实现提供的默认实现,我们可以这样做,如下所示,
public class Impl implements InterfaceA, InterfaceB {
public void defaultMethod(){
// existing code here..
InterfaceA.super.defaultMethod();
}
}
Run Code Online (Sandbox Code Playgroud)
我们可以选择任何默认实现或两者作为新方法的一部分.
Java接口静态方法,代码示例,静态方法与默认方法
Java接口静态方法类似于默认方法,除了我们不能在实现类中覆盖它们.此功能有助于我们避免在实现类中实现不良而导致的不良结果.让我们用一个简单的例子来看一下.
public interface MyData {
default void print(String str) {
if (!isNull(str))
System.out.println("MyData Print::" + str);
}
static boolean isNull(String str) {
System.out.println("Interface Null Check");
return str == null ? true : "".equals(str) ? true : false;
}
}
Run Code Online (Sandbox Code Playgroud)
现在让我们看一个具有isNull()方法但实现较差的实现类.
public class MyDataImpl implements MyData {
public boolean isNull(String str) {
System.out.println("Impl Null Check");
return str == null ? true : false;
}
public static void main(String args[]){
MyDataImpl obj = new MyDataImpl();
obj.print("");
obj.isNull("abc");
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,isNull(String str)是一个简单的类方法,它不会覆盖接口方法.例如,如果我们将@Override注释添加到isNull()方法,则会导致编译器错误.
现在,当我们运行应用程序时,我们得到以下输出.
接口空检查
Impl Null Check
如果我们将接口方法从static变为default,我们将得到以下输出.
Impl Null Check
MyData打印::
Impl Null Check
Java接口静态方法只对接口方法可见,如果我们从MyDataImpl类中删除isNull()方法,我们将无法将它用于MyDataImpl对象.但是,与其他静态方法一样,我们可以使用类名称的接口静态方法.例如,有效的语句将是:
boolean result = MyData.isNull("abc");
Run Code Online (Sandbox Code Playgroud)
在结束发布之前,我想简要介绍一下Functional接口.只有一个抽象方法的接口称为功能接口.
@FunctionalInterface
引入了一个新的注释来将接口标记为功能接口.@FunctionalInterface
注释是一种避免在功能界面中意外添加抽象方法的工具.使用它是可选的但很好的做法.
功能接口是期待已久的,并且备受追捧Java 8的特性,因为它使我们能够使用lambda表达式来实例化它们.添加了一个带有大量功能接口的新包java.util.function,以便为lambda表达式和方法引用提供目标类型.我们将在以后的帖子中研究功能接口和lambda表达式.
Rav*_*abu 40
除了第一个语句(Java 8发布之后)之外,所有语句都是有效的:
Java接口的方法是隐式抽象的,不能有实现
从文档页面:
接口是一种类似于类的引用类型,它只能包含 常量,方法签名,默认方法,静态方法和嵌套类型
方法体仅适用于默认方法和静态方法.
默认方法:
接口可以具有默认方法,但与抽象类中的抽象方法不同.
默认方法使您能够向库的接口添加新功能,并确保与为这些接口的旧版本编写的代码的二进制兼容性.
扩展包含默认方法的接口时,可以执行以下操作:
abstract
.静态方法:
除了默认方法,您还可以在接口中定义静态方法.(静态方法是一种与定义它的类相关联的方法,而不是与任何对象相关联.该类的每个实例都共享其静态方法.)
这使您可以更轻松地在库中组织辅助方法;
关于interface
拥有static
和default
方法的文档页面中的示例代码.
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));
}
}
Run Code Online (Sandbox Code Playgroud)
使用以下准则选择是使用接口还是抽象类.
接口:
抽象类:
在几个密切相关的类之间共享代码.它建立的是一种关系.
在相关类之间共享共同状态(状态可以在具体类中修改)
相关文章:
Implements vs extends:何时使用?有什么不同?
通过这些例子,您可以理解这一点
不相关的类可以通过接口具有功能,但相关的类通过扩展基类来改变行为.
Vic*_*tor 22
你的解释看起来不错,但可能看起来你正在从教科书中读到这一切?: - /
我更烦恼的是,你的榜样有多坚固?您是否懒得包含抽象和接口之间几乎所有的差异?
就个人而言,我建议这个链接:http: //mindprod.com/jgloss/interfacevsabstract.html#TABLE
一份详尽的差异清单..
希望它能帮助您和所有其他读者在未来的采访中
Ser*_*riu 21
许多初级开发人员错误地认为接口,抽象和具体类是同一事物的微小变化,并且纯粹基于技术理由选择其中一个:我是否需要多重继承?我需要一些地方来放置常用方法吗?我是否需要打扰除了具体课程之外的其他事情?这是错误的,隐藏在这些问题中的主要问题是:"我".当您自己编写代码时,您很少会想到其他现有或未来的开发人员在使用代码或使用代码.
接口和抽象类虽然从技术角度来看显然相似,但具有完全不同的含义和目的.
接口定义了一些实现将为您实现的合同.
抽象类提供了一个默认的行为是您的实现可以重复使用.
以上这两点是我在面试时所寻找的,并且是一个足够紧凑的摘要.请阅读以获得更多详情.
换句话说:具体的课程以非常具体的方式完成实际工作.例如,ArrayList
使用连续的存储区域以紧凑的方式存储对象列表,其提供快速随机访问,迭代和就地更改,但是在插入,删除和偶尔添加时是可怕的; 同时,a LinkedList
使用双链节点来存储对象列表,而是提供快速迭代,就地更改和插入/删除/添加,但随机访问非常糟糕.这两种类型的列表针对不同的用例进行了优化,对于您将如何使用它们非常重要.当你试图从你正在与之大量交互的列表中挤出性能时,当你选择列表类型时,你应该仔细挑选你正在实例化的列表.
另一方面,列表的高级用户并不真正关心它是如何实际实现的,并且它们应该与这些细节隔离.让我们假设Java没有公开List
接口,但只有一个具体的List
类,实际上LinkedList
是现在的.所有Java开发人员都会定制他们的代码以适应实现细节:避免随机访问,添加缓存以加速访问,或者只是ArrayList
自己重新实现,尽管它与所有其他实际List
只能使用的代码不兼容.那将是可怕的...但现在想象一下,Java大师实际上意识到链接列表对于大多数实际用例来说是可怕的,并且决定切换到他们唯一List
可用类的数组列表.这会影响世界上每个Java程序的性能,人们也不会对此感到高兴.主要的罪魁祸首是实施细节可用,开发人员认为这些细节是他们可以依赖的永久合同.这就是为什么隐藏实现细节很重要,并且只定义抽象契约.这是一个接口的目的:定义一个方法接受什么样的输入,以及期望什么样的输出,而不暴露所有会诱使程序员调整他们的代码以适应可能随任何未来更新而改变的内部细节的内容.
接口和具体类之间是一个抽象类.它应该有助于实现共享共同或无聊的代码.例如,AbstractCollection
提供isEmpty
基于大小为0的基本实现,contains
迭代和比较,addAll
重复add
等等.这使实现可以专注于区分它们的关键部分:如何实际存储和检索数据.
接口是代码的不同部分之间的低内聚网关.它们允许库存在和发展,而不会在内部发生变化时破坏每个库用户.它被称为应用程序编程接口,而不是应用程序编程类.在较小的规模上,它们还允许多个开发人员通过良好记录的界面分离不同的模块,从而在大型项目上成功协作.
抽象类是在实现接口时使用的高内聚助手,假设某些级别的实现细节.或者,抽象类用于定义SPI,服务提供者接口.
API和SPI之间的区别是微妙的,但很重要:对于API,重点在于谁使用它,而对于SPI,重点在于谁实现它.
向API添加方法很简单,API的所有现有用户仍将编译.向SPI添加方法很难,因为每个服务提供者(具体实现)都必须实现新方法.如果接口用于定义SPI,则提供者必须在SPI合同发生变化时发布新版本.如果使用抽象类,则可以根据现有抽象方法定义新方法,也可以将空方法定义为空throw not implemented exception
存根,这至少允许旧版本的服务实现仍然可以编译和运行.
尽管Java 8引入了接口的默认方法,这使得接口和抽象类之间的界限更加模糊,但这并不是说实现可以重用代码,而是更容易更改既可以作为API又可以作为SPI的接口(或错误地用于定义SPI而不是抽象类).
OP答案中提供的技术细节被认为是"书本知识",因为这通常是在学校和大多数技术书籍中使用的关于语言的方法:什么是事物,而不是如何在实践中使用它,特别是在大规模应用中.
这是一个类比:假设问题是:
为舞会之夜,汽车或酒店房间租用什么更好?
技术答案听起来像:
嗯,在汽车里你可以早点做到,但在酒店房间你可以做得更舒服.另一方面,酒店房间只在一个地方,而在车里,你可以在更多的地方做,比如说,你可以去远景观景点,或在免下车剧院,或许多其他地方,甚至不止一个地方.此外,酒店房间还有淋浴.
这一切都是正确的,但完全错过了两个完全不同的东西,两者可以同时用于不同的目的,而"做它"方面对于两个选项中的任何一个都不是最重要的.答案缺乏视角,它表明了一种不成熟的思维方式,同时正确地呈现了真实的"事实".
接口是一个"契约",实现合同的类承诺实现这些方法.我必须编写一个界面而不是一个类的例子是我将游戏从2D升级到3D时的情况.我必须创建一个界面来在2D和3D版本的游戏之间共享类.
package adventure;
import java.awt.*;
public interface Playable {
public void playSound(String s);
public Image loadPicture(String s);
}
Run Code Online (Sandbox Code Playgroud)
然后我可以基于环境实现方法,同时仍然能够从不知道正在加载的游戏版本的对象调用这些方法.
public class Adventure extends JFrame implements Playable
public class Dungeon3D extends SimpleApplication implements Playable
public class Main extends SimpleApplication implements AnimEventListener,
ActionListener, Playable
通常,在游戏世界中,世界可以是一个在游戏上执行方法的抽象类:
public abstract class World...
public Playable owner;
public Playable getOwner() {
return owner;
}
public void setOwner(Playable owner) {
this.owner = owner;
}
Run Code Online (Sandbox Code Playgroud)
怎么想以下方式:
因此,当你有一个抽象类Mammals,一个子类Human和一个接口Driving时,你可以说
我的建议是,书本知识短语表明他想要听到两者之间的语义差异(就像其他人已经建议的那样).
小智 6
抽象类不是纯粹的抽象bcz它的具体(实现方法)集合以及未实现的方法.但是接口是纯粹的抽象bcz只有未实现的方法而不是具体的方法.
为什么抽象类?
为什么接口?
小智 5
我观察到的主要区别是抽象类为我们提供了一些已经实现的常见行为,而子类只需要实现与它们对应的特定功能。其中 as for 接口只会指定需要完成哪些任务,接口不会给出任何实现。我可以说它指定了它自己和实现的类之间的契约。
归档时间: |
|
查看次数: |
424949 次 |
最近记录: |