为什么Java不允许多重继承,但允许使用默认实现符合多个接口

2022-08-31 20:31:33

我不是在问这个 -> 为什么 Java 中没有多重继承,但允许实现多个接口?

在Java中,不允许多重继承,但是,在Java 8之后,接口可以具有默认方法(可以实现方法本身),就像抽象类一样。在此上下文中,还应允许多重继承。

interface TestInterface 
{ 
    // abstract method 
    public void square(int a); 

    // default method 
    default void show() 
    { 
      System.out.println("Default Method Executed"); 
    } 
} 

答案 1

事情并没有那么简单。
如果某个类实现了多个接口,这些接口使用相同的签名定义默认方法,则编译器将强制您为该类重写此方法。

例如,使用这两个接口:

public interface Foo {
    default void doThat() {
        // ...
    }
}

public interface Bar {    
    default void doThat() {
        // ...
    }       
}

它不会编译:

public class FooBar implements Foo, Bar{
}

您应该定义/重写该方法来消除歧义。
例如,您可以委托给实现,例如:Bar

public class FooBar implements Foo, Bar{    
    @Override
    public void doThat() {
        Bar.super.doThat();
    }    
}

或委托给实现,例如:Foo

public class FooBar implements Foo, Bar {
    @Override
    public void doThat() {
        Foo.super.doThat();
    }
}

或者仍然定义另一种行为:

public class FooBar implements Foo, Bar {
    @Override
    public void doThat() {
        // ... 
    }
}

该约束表明,即使对于接口默认方法,Java 也不允许多重继承。


我认为我们不能对多重继承应用相同的逻辑,因为可能会发生多重问题,其主要情况是:

  • 重写/删除两个继承类中方法的歧义可能会引入副作用,如果继承类在内部依赖于此方法,则会更改它们的整体行为。对于默认接口,这种风险也存在,但应该不那么罕见,因为默认方法不是为了引入复杂的处理(例如类内部的多个内部调用)而设计的,或者是有状态的(实际上接口不能承载实例字段)。
  • 如何继承多个字段?即使语言允许,你也会遇到与前面引用的完全相同的问题:继承类行为的副作用:在一个和类中定义的字段,你想要子类,没有相同的含义和意图。int fooAB

答案 2

语言设计人员已经考虑过了,所以这些东西是由编译器强制执行的。因此,如果您定义:

interface First {
    default void go() {
    }
}

interface Second {
    default void go() {
    }
}

并且您为这两个接口实现了一个类:

static class Impl implements First, Second {

}

您将收到编译错误;并且您需要覆盖以免围绕它产生歧义。go

但是你可能会想,你可以在这里欺骗编译器,通过做:

interface First {
    public default void go() {
    }
}

static abstract class Second {
    abstract void go();
}

static class Impl extends Second implements First {
}

你可能会认为它已经提供了一个实现,它应该没问题。这太照顾了,因此这也不会编译。First::goSecond::go

JLS 9.4.1.3 :类似地,当抽象和具有匹配签名的默认方法被继承时,我们会产生错误。在这种情况下,可以优先考虑其中一个 - 也许我们会假设默认方法也为抽象方法提供了合理的实现。但这是有风险的,因为除了巧合的名称和签名之外,我们没有理由相信默认方法的行为与抽象方法的契约一致 - 默认方法在最初开发子接口时甚至可能不存在。在这种情况下,更安全的做法是要求用户主动断言默认实现是合适的(通过重写声明)。

我要引入的最后一点,以巩固即使Java中添加了新内容也不允许多重继承,即来自接口的静态方法不会被继承。默认情况下,静态方法被继承

static class Bug {
    static void printIt() {
        System.out.println("Bug...");
    }
}

static class Spectre extends Bug {
    static void test() {
        printIt(); // this will work just fine
    }
}

但是,如果我们为一个接口改变这一点(并且您可以实现多个接口,这与类不同):

interface Bug {
    static void printIt() {
        System.out.println("Bug...");
    }
}

static class Spectre implements Bug {
    static void test() {
        printIt(); // this will not compile
    }
}

现在,编译器禁止这样做,并且也是如此:JLS

JLS 8.4.8 : 一个类不会从它的超接口继承静态方法。


推荐