我已经在Java中阅读了很多关于接口和类继承的内容,我知道如何做到这两点,我觉得我对两者都有很好的感觉。但似乎没有人真正将两者并排比较,并解释了何时以及为什么要使用其中一种。我没有发现很多次实现接口比扩展超类更好的系统。
那么什么时候实现一个接口,什么时候扩展超类?
答案 0 :(得分:38)
如果要定义合同,请使用界面。即X必须取Y并返回Z.它不关心代码如何。一个类可以实现多个接口。
如果要在非抽象方法中定义默认行为,请使用抽象类,以便最终用户可以重复使用它,而无需一次又一次地重写它。一个类可以从只扩展一个其他类。仅使用 抽象方法的抽象类可以像界面一样好。没有任何抽象方法的抽象类可以被识别为Template Method模式(对于一些真实世界的例子,请参见this answer)。
当您希望在定义默认行为时为最终用户提供自由时,抽象类可以完美地实现接口。
答案 1 :(得分:12)
如果您想要的是定义合同,即您希望继承类实现的方法签名,则应选择接口。接口根本没有实现。继承类可以自由选择自己的实现。
有时您希望在基类型中定义部分实现,并希望将其余部分留给继承类。如果是这种情况,请选择抽象类。抽象类可以定义方法实现和变量,同时将一些方法保留为抽象。扩展类可以选择如何实现抽象方法,同时它们也具有超类提供的部分实现。
抽象类的一个极端是纯抽象类 - 一个只有抽象方法而没有其他方法的类。如果涉及纯抽象类与界面,请使用界面。 Java只允许单个实现继承,而它允许多个接口继承,这意味着一个类可以实现多个接口,但只能扩展一个类。因此,在接口上选择纯抽象类意味着在实现抽象方法时不允许子类扩展任何其他类。
答案 2 :(得分:7)
使用定义行为的界面。用户(抽象)类(和子类)提供实现。它们不是相互排斥的;他们都可以一起工作。
例如,假设您正在定义数据访问对象。您希望DAO能够加载数据。所以在接口上加载一个load方法。这意味着任何想要将自己称为DAO的东西都必须实现加载。现在假设您需要加载A和B.您可以创建一个参数化(泛型)的通用抽象类,以提供有关加载如何工作的大纲。然后,您将该抽象类子类化,以提供A和B的具体实现。
答案 3 :(得分:3)
没人?
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);
}
}
答案 4 :(得分:3)
使用抽象类和接口的主要原因是不同的。
当您拥有对一堆方法具有相同实现的类时,应该使用抽象类,但这些类会有所不同。
这可能是一个不好的例子,但Java框架中最明显的抽象类使用是在java.io类中。 OutputStream
只是一个字节流。该流的位置完全取决于您使用的OutputStream
的哪个子类... FileOutputStream
,PipedOutputStream
,从java.net.Socket
的{{1}}创建的输出流1}}方法......
注意:java.io也使用Decorator模式将流包装在其他流/读者/编写器中。
当你只想保证一个类实现一组方法但你不关心如何使用时,应该使用一个接口。
接口最明显的用途是在Collections框架内。
我不关心getOutputStream
如何添加/删除元素,只要我可以调用List
和add(something)
来放置和获取元素。它可以使用数组(get(0)
,ArrayList
),链接列表(CopyOnWriteArrayList
)等...
使用接口的另一个好处是类可以实现多个。 LinkedList
是LinkedList
和 List
的实现。
答案 5 :(得分:2)
我认为当你使用它们表示对象具有某个属性或行为时,接口最有效,跨越多个继承树,并且只为每个类明确定义。
例如,想想可比较。如果你想创建一个类Comparable以便被其他类扩展,那么它必须在继承树上非常高,可能就在Object之后,并且它表达的属性是可以比较该类型的两个对象,但是通常无法定义(你不能直接在Comparable类中实现compareTo,它对于实现它的每个类都是不同的。)
当他们定义明确的内容,你知道他们拥有什么属性和行为,以及你想要传递给孩子们的方法的实际实现时,类最有效。
因此,当您需要定义像人类或汽车这样的具体对象时,类可以工作,并且当您需要更加抽象的行为时,接口可以更好地工作,这些行为过于笼统,不属于任何继承树,比如能够进行比较(可比较) )或运行(Runnable)。
答案 6 :(得分:2)
在interface
和base class
之间进行选择的一种方法是考虑代码所有权。如果您控制所有代码,那么基类是可行的选择。另一方面,如果许多不同的公司可能想要生产可更换的组件,那就是定义合同,那么接口是您唯一的选择。
答案 7 :(得分:1)
我发现了一些文章,尤其是一些描述为什么不应该使用实现继承(即超类)的文章:
答案 8 :(得分:1)
我想我会给出经典汽车的例子。
当您拥有汽车界面时,您可以创建福特,雪佛兰和奥兹莫比尔。换句话说,您可以通过汽车界面创建不同类型的汽车。
当你上车时,你可以延长汽车类别来制造卡车或公共汽车。换句话说,您可以在保留基类或超类的属性的同时向子类添加新属性。
答案 9 :(得分:1)
如果派生类属于同一类型,你可以考虑从超类扩展。我的意思是当一个类扩展一个抽象类时,它们都应该是同一类型,唯一的区别是超类具有更一般的行为,子类具有更具体的行为。界面是一个完全不同的概念。当一个类实现一个接口时,它要么暴露一些API(契约),要么得到某些行为。举个例子,我会说Car是一个抽象类。您可以从福特,雪佛兰等各种类型的车中扩展许多类。但是如果你需要某些特定的行为,比如你需要在汽车中使用GPS,那么具体的类别,例如福特应该实现GPS接口。
答案 10 :(得分:0)
如果您只想在子类中继承方法签名(名称,参数,返回类型),请使用接口,但如果您还想继承实现代码,请使用超类。