CodeGym /课程 /JAVA 25 SELF /接口的多重实现

接口的多重实现

JAVA 25 SELF
第 20 级 , 课程 2
可用

1. 引言

在 Java 中,不能同时继承多个类。这是为了避免“菱形继承问题”(diamond problem)——当两个父类都定义了同名方法时,不清楚应采用哪一个实现。但接口可以实现任意多个!为什么?因为接口只是契约,不包含实现(Java 8 之前没有实现;自 Java 8 起允许有 defaultstatic 方法)。因此不会产生代码继承上的混乱。

这就像现实生活中的情况:你可以同时是“司机”、“电脑用户”和“游泳者”。这些“接口”各自描述了一组能力,但并不会强迫你变成另一个人的复制品。

多接口实现的语法

在 Java 中,类可以在关键字 implements 之后用逗号分隔列出多个接口。下面是一个基础示例:

public interface Movable {
    void move(int x, int y);
}

public interface Chargeable {
    void charge();
}

public class Robot implements Movable, Chargeable {
    @Override
    public void move(int x, int y) {
        System.out.println("机器人移动到点 (" + x + ", " + y + ")");
    }

    @Override
    public void charge() {
      System.out.println("机器人正在充电。");
    }
}

在这个示例中,Robot 很万能:既能移动又能充电。就像现实中一样,技能越多,被邀请去面试的机会就越多!

2. 为什么需要这样做?实践示例

示例 1:对象的不同“角色”

设想你在设计一个游戏角色:

  • 它可以移动(Movable
  • 可以攻击(Attackable
  • 可以保存到文件(Serializable —— 标准 Java 库中就有这个接口)
public interface Attackable {
    void attack();
}

public class Hero implements Movable, Attackable, java.io.Serializable {
    @Override
    public void move(int x, int y) {
        System.out.println("英雄移动到新位置。");
    }

    @Override
    public void attack() {
        System.out.println("英雄发动攻击!");
    }
}

现在你的类可以用于各种不同的上下文:它可以被传入任何要求上述任一接口的方法中。

示例 2:组合标准接口

在标准 Java 库中,常见的接口有 Comparable(用于比较对象)和 Serializable(用于把对象保存到文件或通过网络传输)。有时我们希望对象两者兼具:

public class Person implements Comparable<Person>, java.io.Serializable {
    private String name;
    private int age;

    public Person(String name, int age) { this.name = name; this.age = age; }

    @Override
    public int compareTo(Person other) {
        return Integer.compare(this.age, other.age);
    }
}

现在可以对 Person 对象进行排序(例如在列表中),也可以把它们写入文件。

3. 特点与限制

每个方法只需一份实现

如果两个接口定义了签名相同的方法,你只需实现一次。示例:

public interface A {
    void doSomething();
}
public interface B {
    void doSomething();
}
public class MyClass implements A, B {
    @Override
    public void doSomething() {
        System.out.println("为两个接口提供的 doSomething 实现。");
    }
}

Java 不会报错——关键是签名必须一致。如果方法签名不同,它们就被视为不同的方法——每个都需要实现。

不存在“菱形继承问题”

与多重继承类不同,实现多个接口不会出现继承到同一个方法的两个不同实现的情况。Java 8 之前接口根本没有实现,而自从出现 default 方法之后——如果发生冲突,你必须显式地解决它(详细内容见下一讲)。

没有状态

接口不能包含普通字段(只能有常量——public static final)。因此也不会出现“两个父类型中同名字段”的混淆。

4. 示例:在一个类中实现多个接口

我们给示例应用(比如一个动物园)增加一些能力。假设有一些动物,既能移动也能发出声音:

public interface Movable {
    void move(int x, int y);
}

public interface Soundable {
    void makeSound();
}

public class Dog implements Movable, Soundable {
    private String name;

    public Dog(String name) {
        this.name = name;
    }

    @Override
    public void move(int x, int y) {
        System.out.println(name + " 跑向 (" + x + ", " + y + ")");
    }

    @Override
    public void makeSound() {
        System.out.println(name + " 说:汪汪!");
    }
}

public class Cat implements Movable, Soundable {
    private String name;

    public Cat(String name) {
        this.name = name;
    }

    @Override
    public void move(int x, int y) {
        System.out.println(name + " 悄悄地靠近 (" + x + ", " + y + ")");
    }

    @Override
    public void makeSound() {
        System.out.println(name + " 说:喵!");
    }
}

现在我们可以编写通用方法来处理任何“会移动”或“会发声”的对象:

public static void testMovable(Movable m) {
    m.move(10, 20);
}

public static void testSoundable(Soundable s) {
    s.makeSound();
}

public static void main(String[] args) {
    Dog rex = new Dog("雷克斯");
    Cat murka = new Cat("穆尔卡");

    testMovable(rex);       // 雷克斯 跑向 (10, 20)
    testSoundable(murka);   // 穆尔卡 说:喵!
}

当然,如果对象同时实现了两个接口,它既可以传给这边的方法,也可以传给那边的方法!

6. 有用的细节

如果接口发生冲突?

有时,两个接口会定义签名相同但语义不同的方法。比如,一个接口期望方法 reset() 重置坐标,而另一个则期望这个方法关闭设备。这种情况下需要格外谨慎:方法仍然只能实现一次,并且需要“同时具备”两种行为(或者至少选定一种要做的事情)。现实中这种情况不多见,但一旦遇到,就应该反思设计是否合理。

包含实现不同接口对象的集合示例

假设我们有一个包含实现不同接口的对象的列表。我们可以遍历它们并调用需要的方法:

Movable[] movables = {
    new Dog("沙里克"),
    new Cat("巴尔西克"),
    new Robot()
};

for (Movable m : movables) {
    m.move(0, 0);
}

对任意接口都可以做类似的事情。

7. 多接口实现中的常见错误

错误 1:未实现接口的所有方法。
如果类声明实现了某个接口,但少实现了其中任何一个方法——编译器会立即报错。不要遗漏任何方法,即使它们看起来“多余”。

错误 2:具有相同签名的冲突方法。
如果两个接口定义了同名同签名的方法,只需实现一次。但如果这些方法的语义不同,可能会导致混乱和缺陷。在这种情况下,最好重新审视架构设计。

错误 3:在类中用 extends 来“继承”接口。
在类中实现接口必须使用 implements,而不是 extends。例如:

public class MyClass implements A, B { ... } // 正确
public class MyClass extends A, B { ... }    // 错误!

错误 4:试图直接创建接口对象。
接口是契约,不能直接创建:

Movable m = new Movable(); // 编译错误

只能创建实现了该接口的类的对象。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION