在我的一次采访中,我被要求解释接口类和抽象类之间的区别。
以下是我的回答:
Methods of a Java interface are implicitly abstract
and cannot have implementations. A Java abstract class can have
instance methods that implements a default behaviour.
Variables declared in a Java interface are by default final. An
abstract class may contain non-final variables.
Members of a Java interface are public by default. A Java abstract
class can have the usual flavours of class members like private,
protected, etc.
A Java interface should be implemented using keyword “implements”; A
Java abstract class should be extended using keyword “extends”.
An interface can extend another Java interface only, an abstract class
can extend another Java class and implement multiple Java interfaces.
A Java class can implement multiple interfaces but it can extend only
one abstract class.
然而,面试官并不满意,他告诉我这种描述代表了“书本知识”。
他让我给出一个更实际的回答,用实际的例子解释我什么时候会选择抽象类而不是接口。
我哪里错了?
接口由单例变量(公共静态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...
}
}
在上面的例子中,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()方法),然后将此任务类的对象传递给线程实例并调用其start()方法。
现在你可以问,如果Runnable是一个抽象类呢?
从技术上讲,这是可能的,但从设计角度来看,这是一个糟糕的选择原因:
Runnable没有与之相关的状态,也没有“提供”任何状态
run()方法的默认实现
Task必须扩展它,因此它不能扩展任何其他类
Task没有提供任何专门化到Runnable类,它所需要的只是重写run()方法
换句话说,Task类需要在线程中运行的能力,这是通过实现Runnable接口而实现的,而扩展thread类则使其成为线程。
简单地把我们的接口定义为一种能力(契约),而使用
的抽象类,用于定义的框架(公共/部分)实现
它。
免责声明:下面是愚蠢的例子,尽量不要判断:-P
interface Forgiver {
void forgive();
}
abstract class GodLike implements Forgiver {
abstract void forget();
final void forgive() {
forget();
}
}
现在你可以选择成为神一样的人,但你可以选择只成为宽恕者(即不成为神一样的人),并做:
class HumanLike implements Forgiver {
void forgive() {
// forgive but remember
}
}
或者你可以选择像上帝一样去做:
class AngelLike extends GodLike {
void forget() {
// forget to forgive
}
}
P.S.与java 8接口也可以有静态以及默认(可重写实现)方法,因此区别b/w接口和抽象类甚至更窄。
当我试图在两个密切相关的类之间共享行为时,我创建了一个包含公共行为的抽象类,并作为两个类的父类。
当我试图定义Type(对象的用户可以可靠地调用的方法列表)时,我创建了一个接口。
例如,我绝不会创建一个只有一个具体子类的抽象类,因为抽象类是关于共享行为的。但是我很可能创建一个只有一个实现的接口。我的代码的用户不会知道只有一个实现。实际上,在未来的版本中可能会有几个实现,它们都是一些新的抽象类的子类,这些抽象类在我创建接口时甚至还不存在。
这似乎也有点太书生气了(尽管我在记忆中从未见过这样的说法)。如果面试官(或OP)真的想要更多关于这方面的个人经验,我早就准备好了关于界面是出于必要而进化的轶事,反之亦然。
One more thing. Java 8 now allows you to put default code into an interface, further blurring the line between interfaces and abstract classes. But from what I have seen, that feature is overused even by the makers of the Java core libraries. That feature was added, and rightly so, to make it possible to extend an interface without creating binary incompatibility. But if you are making a brand new Type by defining an interface, then the interface should be JUST an interface. If you want to also provide common code, then by all means make a helper class (abstract or concrete). Don't be cluttering your interface from the start with functionality that you may want to change.
你的回答是对的,但是面试官需要你从软件工程的角度来区分,而不是根据Java的细节。
简单的单词:
An Interface is like the interface of a shop anything that is shown on it should be there in the shop, so any method in the Interface must be there implemented in the concrete class. Now what if some classes share some exact methods and varies in others. Suppose the Interface is about a shop that contains two things and suppose we have two shops both contain sport equipment but one has clothes extra and the other has shoes extra. So what you do is making an abstract class for Sport that implements the Sports method and leave the other method unimplemented. Abstract class here means that this shop doesn't exist itself but it is the base for other classes/shops. This way you are organising the code, avoiding errors of replicating the code, unifying the code, and ensuring re-usability by some other class.
你很好地总结了使用和实现方面的实际差异,但没有提到意义上的差异。
接口是实现类将具有的行为的描述。实现类确保它将拥有这些可以在其上使用的方法。它基本上是类必须做出的契约或承诺。
抽象类是不同子类的基础,这些子类共享不需要重复创建的行为。子类必须完成行为,并有覆盖预定义行为的选项(只要它没有被定义为final或private)。
你会在java中找到很好的例子。util包,它包含了List这样的接口和AbstractList这样已经实现了接口的抽象类。官方文档对AbstractList的描述如下:
该类提供了List接口的框架实现,以最大限度地减少实现该接口所需的工作,该接口由“随机访问”数据存储(例如数组)支持。