在我的一次采访中,我被要求解释接口类和抽象类之间的区别。
以下是我的回答:
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.
然而,面试官并不满意,他告诉我这种描述代表了“书本知识”。
他让我给出一个更实际的回答,用实际的例子解释我什么时候会选择抽象类而不是接口。
我哪里错了?
我将尝试用实际场景来回答,以说明两者之间的区别。
接口是零负载的,即不需要维护状态,因此将契约(能力)与类关联是更好的选择。
例如,说我有一个执行一些操作的任务类,现在在单独的线程中执行一个任务,我不需要扩展线程类,更好的选择是使任务实现可运行的接口(即实现其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接口和抽象类甚至更窄。
我先给大家举个例子:
public interface LoginAuth{
public String encryptPassword(String pass);
public void checkDBforUser();
}
假设您的应用程序中有3个数据库。然后,该数据库的每个实现都需要定义上述2个方法:
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
}
但是,如果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();
}
现在,在每个子类中,我们只需要实现一个方法——依赖于数据库的方法。
我相信面试官想要了解的可能是界面和实现之间的区别。
代码模块的接口——不是Java接口,更通用的说法是“接口”——基本上是与使用该接口的客户端代码之间的契约。
代码模块的实现是使模块工作的内部代码。通常,您可以以多种不同的方式实现特定的接口,甚至可以在客户机代码不知道更改的情况下更改实现。
A Java interface should only be used as an interface in the above generic sense, to define how the class behaves for the benefit of client code using the class, without specifying any implementation. Thus, an interface includes method signatures - the names, return types, and argument lists - for methods expected to be called by client code, and in principle should have plenty of Javadoc for each method describing what that method does. The most compelling reason for using an interface is if you plan to have multiple different implementations of the interface, perhaps selecting an implementation depending on deployment configuration.
A Java abstract class, in contrast, provides a partial implementation of the class, rather than having a primary purpose of specifying an interface. It should be used when multiple classes share code, but when the subclasses are also expected to provide part of the implementation. This permits the shared code to appear in only one place - the abstract class - while making it clear that parts of the implementation are not present in the abstract class and are expected to be provided by subclasses.
接口由单例变量(公共静态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.
}
}
如果我们将来在给定的抽象类中添加方法(具体的/抽象的),那么实现类将不需要更改其代码。但是,如果将来在接口中添加方法,则必须将实现添加到实现该接口的所有类中,否则会发生编译时错误。
还有其他不同之处,但这些都是面试官所期望的。希望这对你们有帮助。
你很好地总结了使用和实现方面的实际差异,但没有提到意义上的差异。
接口是实现类将具有的行为的描述。实现类确保它将拥有这些可以在其上使用的方法。它基本上是类必须做出的契约或承诺。
抽象类是不同子类的基础,这些子类共享不需要重复创建的行为。子类必须完成行为,并有覆盖预定义行为的选项(只要它没有被定义为final或private)。
你会在java中找到很好的例子。util包,它包含了List这样的接口和AbstractList这样已经实现了接口的抽象类。官方文档对AbstractList的描述如下:
该类提供了List接口的框架实现,以最大限度地减少实现该接口所需的工作,该接口由“随机访问”数据存储(例如数组)支持。