如何理解 Java 中的接口(interface)?

发布于:2023-03-12 ⋅ 阅读:(188) ⋅ 点赞:(0)

Java 中的接口(interface)如何理解?

interface 的实现(implement)不同于类(class)的继承(extend),因为 interface 中不包含方法(method)的实现(implement)。

于是很多人就会疑惑,既然 interface 中的 method 没有 implement,那么 extend(implement)它做什么呢?

这些人实际上把 interface 的 implement 和 class 的 extend 混淆了——class 的 extend 中,子类可以从父类中取得一些实现(implement),从而实现(realize)代码复用

但是 interface 恰恰不是用来 realize 代码复用的。

为了表现 interface 的作用,我们来看一个例子。

这是一个 class (修饰符已略去)

class Tiger
{
    void run()
    {
        // method body
    }
    void eat()
    {
        // method body
    }
}

可以看到有 2 个方法。

Java 是一门静态类型语言,我们可以用 class 名作为参数类型,这是众所周知的。

void feed(Tiger tiger)
{
    tiger.eat()
}

但是,如果我们有了新的需求呢?

例如,我们又有了一个新的 class

class Fish
{
    void swim()
    {
        // method body
    }
    void eat()
    {
        // method body
    }
}

这个Fishclass 和Tigerclass 一样有一个eat()method,我们希望feedmethod 也能对Fish对象起作用。

当然,我们可以使用 method 的重载(overload)。

void feed(Tiger tiger)
{
    tiger.eat()
}
void feed(Fish fish)
{
    fish.eat()
}

不过可以遇见,要是这样的 class 越来越多,我们的工作量就很大了。更何况,这里的代码基本上完全重复了嘛。

这说明什么?说明 overload 的方案的扩展性并不强,我们需要一个一劳永逸的方案。

理论上,我们可以采用这么一个方案

维护一个集合(set) FeedableAnimals,用来记录那些我们希望feedmethod 能起作用的 class。然后我们的feedmethod 就这么写。(Java 不支持这种语法,因此你只能在脑中这么做)

void feed(FeedableAnimials animal)
{
    animal.eat()
}

然后把TigerFish 等等放进 FeedableAnimals里。

现在,我就明确告诉你,这个FeedableAnimals就是 interface 概念的雏形。

现在,我们离真正的 interface 只差几步了。

考虑我们现在这个方案,看起来比原来好多了。

但是仔细想想,还有一些不足

第一,FeedableAnimals作为一个 set,是需要维护的:如果我们添加新的一种 class,比如Bear,那么我们必须修改其定义,将这个 classBear添加进去。

显然,这不符合开闭原则——对修改封闭。

第二,假设我们把Bear添加进来,但是由于疏忽,我们没有确认Bear是否有一个eat()method。不妨假设,Bear实际上长这样

class Bear
{
    void run()
    {
        // method body
    }
    void eat(Fish fish)
    {
        // method body
    }
}

这时Bear有一个methodeat(Fish),于是我们的工作人员产生了疏忽,误以为Bear有一个eat()method(这两者的方法签名(Method Signature)并不相同!)就把它加了进来。

固然,我们可以人工一个个浏览FeedableAnimals中的 class,检查其是否具有正确的 method signature。但是如果能让编译器来做这件事显然更合理,因为保证语法没有错误是编译器的工作。

于是,在 Java 中,真正的 interface 是这样设计的:

  1. interface 并不记录 class,相反,是 class 记录 interface。
  2. interface 可以定义抽象 method,抽象 method 仅仅包含 method signature。如果 class implement 了 interface,那么编译器会检查 class 中是否有对应的 method signature。

可以看到,这样的设计克服了上述两点困难。我们可以回到上面那个例子看一看。

定义一个 interface FeedableAnimals

interface FeedableAnimals
{
    void eat();
}

然后feedmethod 应该这么写:

void feed(FeedableAnimals animal)
{
    animal.eat()
}

这时,由于 interfaceFeedableAnimals中确实规定了 method signatureeat(),因此编译器能够保证对于任何一个 implement 了 FeedableAnimals 的对象 animal 来说,animal.eat() 的调用不会出现语法错误(例如animal 找不到 eat() 方法这样的报错)。这样,从编译器层面就排除了feedmethod 跑不通的可能性。

Bear class 应该这么写

class Bear implements FeedableAnimals
{
    void eat()
    {
        // method implement
    }
}

这样一旦出现诸如 method signature 不符、或者是 method 忘了实现这样的错误,由于我们已经声明了Bear是 implement 了 interface FeedableAnimals 的,因此编译器就能帮我们检查和发现这些错误,从而在编译阶段就杜绝这些错误的可能性。

并且以后再出现新的 class,我们也只需要像Bear那样写,就能保证feedmethod 能自动被应用在这些新 class 的对象上。


网站公告

今日签到

点亮在社区的每一天
去签到