40

我已经阅读了很多关于 Java 中的接口和类继承的内容,并且我知道如何做到这两点,而且我认为我对两者都有很好的感觉。但似乎没有人真正将两者并排比较并解释您何时以及为什么要使用其中一个。很多时候我都没有发现实现接口比扩展超类更好的系统。

那么什么时候实现一个接口,什么时候扩展一个超类呢?

4

11 回答 11

42

如果要定义合同,请使用接口。即 X 必须取 Y 并返回 Z。它不关心代码是如何做到的。一个类可以实现多个接口。

如果您想在非抽象方法中定义默认行为,请使用抽象类,以便最终用户可以重用它而无需一次又一次地重写它。一个类只能从另一个类扩展。只有抽象方法的抽象类可以像接口一样定义好。没有任何抽象方法的抽象类可以识别为模板方法模式(有关一些真实世界的示例,请参见此答案)。

反过来,只要您想为最终用户提供定义默认行为的自由,抽象类就可以完美地实现接口。

于 2010-07-22T18:21:13.917 回答
13

如果您只想定义一个契约,即您希望继承类实现的方法签名,您应该选择一个接口。接口可以根本没有实现。继承类可以自由选择自己的实现。

有时您想在基类型中定义部分实现,并希望将其余部分留给继承类。如果是这种情况,请选择一个抽象类。抽象类可以定义方法实现和变量,同时将一些方法保留为抽象。扩展类可以选择如何实现抽象方法,同时它们也有超类提供的部分实现。

抽象类的一个极端是纯抽象类——一个只有抽象方法而没有别的东西。如果涉及纯抽象类与接口请使用接口。Java只允许单个实现继承,而它允许多个接口继承,这意味着一个类可以实现多个接口但只能扩展一个类。因此,在接口上选择纯抽象类将意味着在实现抽象方法时,不允许子类扩展任何其他类。

于 2010-07-23T00:43:26.777 回答
8

使用接口来定义行为。用户(抽象)类(和子类)提供实现。它们不是相互排斥的;他们都可以一起工作。

例如,假设您正在定义一个数据访问对象。你希望你的 DAO 能够加载数据。所以在接口上放了一个load方法。这意味着任何想称自己为 DAO 的东西都必须实现加载。现在假设您需要加载 A 和 B。您可以创建一个参数化(泛型)的通用抽象类,以提供有关加载工作原理的概要。然后,您将该抽象类子类化以提供 A 和 B 的具体实现。

于 2010-07-22T18:20:37.240 回答
4

没有人?

http://mindprod.com/jgloss/interfacevsabstract.html

编辑:我应该提供的不仅仅是一个链接

这是一个情况。为了构建下面的汽车示例,请考虑这个

interface Drivable {
    void drive(float miles);
}

abstract class Car implements Drivable { 
    float gallonsOfGas;
    float odometer;
    final float mpg;
    protected Car(float mpg) { gallonsOfGas = 0; odometer = 0; this.mpg = mpg; }
    public void addGas(float gallons) { gallonsOfGas += gallons; }
    public void drive(float miles) { 
        if(miles/mpg > gallonsOfGas) throw new NotEnoughGasException();
        gallonsOfGas -= miles/mpg;
        odometer += miles;
    }
}

class LeakyCar extends Car { // still implements Drivable because of Car
    public addGas(float gallons) { super.addGas(gallons * .8); } // leaky tank
}

class ElectricCar extends Car {
    float electricMiles;
    public void drive(float miles) { // can we drive the whole way electric?
         if(electricMiles > miles) {
             electricMiles -= miles;
             odometer += miles;
             return;                 // early return here
         }
         if(electricMiles > 0) { // exhaust electric miles first
             if((miles-electricMiles)/mpg > gallonsOfGas) 
                 throw new NotEnoughGasException();
             miles -= electricMiles;
             odometer += electricMiles;
             electricMiles = 0;
         }
         // finish driving
         super.drive(miles);
    }
}
于 2010-07-22T17:57:11.653 回答
4

使用抽象类和接口的主要原因是不同的。

当你的类对一堆方法有相同的实现但有一些不同时,应该使用抽象类。

这可能是一个不好的例子,但在 Java 框架中最明显的抽象类使用是在 java.io 类中。 OutputStream只是一个字节流。该流的去向完全取决于OutputStream您使用的是哪个子类... FileOutputStream,,PipedOutputStream从 ajava.net.SocketgetOutputStream方法创建的输出流...

注意:java.io 还使用装饰器模式将流包装在其他流/读取器/写入器中。

当您只想保证一个类实现一组方法但您不在乎如何实现时,应该使用接口。

接口最明显的用途是在 Collections 框架内。

我不在乎 a 如何List添加/删除元素,只要我可以调用add(something)get(0)放置和获取元素。它可以使用数组(ArrayList, CopyOnWriteArrayList)、链表(LinkedList)等...

使用接口的另一个优点是一个类可以实现多个。 LinkedList是 和 的List 实现 Deque

于 2010-07-22T18:40:24.730 回答
2

我认为,当您使用接口来表示对象具有某种属性或行为、跨越多个继承树并且只为每个类明确定义时,接口效果最好。

例如想想 Comparable。如果你想创建一个类 Comparable 以被其他类扩展,它必须在继承树上非常高,可能在 Object 之后,它表示的属性是可以比较该类型的两个对象,但是有通常无法定义(您不能直接在 Comparable 类中实现 compareTo ,实现它的每个类都不同)。

类在定义明确的东西时工作得最好,你知道它们有什么属性和行为,并且有方法的实际实现,你想传递给孩子。

因此,当您需要定义一个具体的对象(例如人或汽车)时,类可以工作,而当您需要更多抽象的行为(这些行为过于笼统而不能属于任何继承树)时,接口会更好地工作,例如比较(Comparable)或可以运行(可运行)。

于 2010-07-22T18:10:51.263 回答
2

One method of choosing between an interface and a base class is the consideration of code ownership. If you control all the code then a base class is a viable option. If on the other hand many different companies might want to produce replaceable components, that is define a contract then an interface is your only choice.

于 2010-07-23T00:51:24.610 回答
1

我发现了一些文章,特别是一些描述了为什么不应该使用实现继承(即超类)的文章:

于 2010-07-22T17:57:14.597 回答
1

我想我会举个老爷车的例子。

当您拥有汽车界面时,您可以创建福特、雪佛兰和奥兹莫比尔。换句话说,您可以从汽车界面创建不同种类的汽车。

当您拥有汽车类时,您可以扩展汽车类以制作卡车或公共汽车。换句话说,您在保留基类或超类的属性的同时向子类添加新属性。

于 2010-07-22T17:57:16.253 回答
1

如果派生类是相同的类型,你可以考虑从超类扩展。我的意思是当一个类扩展一个抽象类时,它们都应该是相同的类型,唯一的区别是超类有更多一般行为和子类有更具体的行为。接口是一个完全不同的概念。当一个类实现一个接口时,它要么公开一些 API(合同),要么获得某些行为。举个例子,我会说 Car 是一个抽象类。您可以从中扩展许多类,例如福特、雪佛兰等,它们都是汽车类型。但是,如果您需要某些特定行为,例如说您需要在汽车中安装 GPS,那么具体的类,例如福特应该实现 GPS 接口。

于 2010-07-22T18:26:16.193 回答
0

如果您只想在子类中继承方法签名(名称、参数、返回类型),请使用接口,但如果您还想继承实现代码,请使用超类。

于 2010-07-22T17:54:07.803 回答