17 January 2014

刚刚看到一篇博客,是一个关于面试问题的思考,问题是:抽象类和接口有什么区别。

要了解它们的区别,首先要知道它们是如何实现的(也就是概念)。

首先要提出抽象方法这个概念,它是如此声明的:

abstract void f();

如果一个类包含一个或多个抽象方法,该类必须声明为抽象类(否则报错)。

abstract class A()

那么什么是抽象方法?抽象方法仅有声明而没有方法主体,即它仅仅提供给你一个接口形式(请注意这里接口的意义),而不提供任何具体实现。如果从一个抽象类继承,并想创建该新类的对象,那么就必须为基类中的所有抽象方法提供方法定义。

abstract class Instrument {
    private int i;
    public abstract void play(Note n);
    public String what() {return "Instrument";}
    public abstract void adjust();
}

class Wind extends Instrument {
    public void play(Note n) {
        print("Wind.play()" + n);
    }
    public String what() {return "Wind";}
    public void adjust() {}
}

上面的例子告诉我们,导出类需要实现抽象基类中抽象方法的具体定义,事实上,what方法不一定需要实现,它不是抽象方法。另外,抽象类也是类,它能够被继承,但仍然无法被多重继承(Java不支持多重继承)。

接口则是一个完全抽象的类,使用interface关键字。所谓完全抽象的类,即它仅仅允许创建者确定方法名、参数列表和返回类型,但是没有任何方法体。就像上面抽象类的一种特殊情况,所有的方法都被声明为抽象类(我们不会这么用,这种情况直接使用接口)。

如果希望某个类遵循某一接口,需要implements关键字。

interface Instrument {
    int VALUE = 5;
    void play(Note n);
    void adjust();
}

class Wind implements Instrument {
    public void play(Note n) {
        print(this + ".play()" + n);
    }
    public String toString() {return "Wind";}
    public void adjust() {print(this + ".adjust()");}
}

interface代替了class,我们可以不在接口中将方法(事实上接口本身也是如此)声明为public,但是编译器会帮我们默认声明。我们使Wind遵循Instrument的接口形式,接口中的方法在导出类中必须被定义。

接口中定义了一个常量(域),它也被自动声明为public,而且还是static和final型的

一个类可以遵循多个接口,同时还能继承某个类(只能继承一个)

public class A() extends B()
        implements C(), D(), E() {
    ...
}

这里类A继承了类B,同时遵循了接口C、D、E,需要注意,这个继承类必须放在接口前面。

我们还可以通过继承来扩展接口。

interface A {
   void f();
}

interface B entends A {
    void g();
}

interface C {
    void h();
}

interface D extends B, C {
    void k();
}

class E implements D {
    void f() {...}
    void g() {...}
    void h() {...}
    void k() {...}
}

这里需要注意的是,接口D继承了两个接口,虽然普通的类不支持多重继承,但是对于接口,这种用法比较特殊,但却可行。类E中实现的四个方法都是必须的,继承接口后,接口相当于被扩展,类似于我们人工扩展接口。

接口还有一些有趣的地方,比如接口可以被嵌套,书中也提到了一些设计模式——策略、适配器和工厂模式,这里不详细介绍了。

我们现在可以回到上面的那个问题——抽象类与接口的区别。这么看来,抽象类提供了一部分抽象和具体实现,而接口完全就是抽象的,没有提供任何实现。

这些都应该是概念上的区别,那么,更进一步的问题是,什么时候使用抽象类,什么时候使用接口。这是一个经验问题,对于我这类新手很难回答,我只能从书中发掘一些线索来尝试回答这个问题:

  • 抽象类是有用的重构工具,它们使得我们可以很容易地将公共方法沿着继承层次结构向上移动。
  • 接口被用来建立类与类之间的协议。
  • 使用接口为了能够向上转型为多个基类型。
  • 使用接口可以防止程序员创建该类的对象,并确保这仅仅是建立一个接口(对于限制不多的情况可以使用抽象类)。
  • 恰当的原则是优先选择类而不是接口(作者说:接口容易被滥用)。

这些都是从书中提炼出来的,没有经过真正的实践,甚至可能是作者的个人想法。最后给出我看过的那篇文章,它的理由比我列出的要高深的多——《抽象类和接口的理解》



blog comments powered by Disqus