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

Why does Java not allow multiple inheritance but does allow conforming to multiple interfaces with default implementations

我不是问这个 -> Why is there no multiple inheritance in Java, but implementing multiple interfaces is allowed?

在Java中不允许多重继承,但是在Java8之后,接口可以有默认方法(可以自己实现方法) ,就像抽象 类。在这种情况下,它也应该允许多重继承。

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

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

Java 不允许字段的多重继承。这在 JVM 中很难支持,因为您只能引用 object 的开头,其中 header 是,而不是任意内存位置。

在Oracle/Openjdk中,objects有一个header后面跟着最超的字段class,然后是下一个最超的class,等。对于不同的子class,允许class 的字段相对于object 的header 以不同的偏移量出现将是一个重大变化。 object 引用很可能必须成为对 object header 的引用和对字段的引用以支持这一点。

事情没有那么简单。
如果 class 实现了多个定义具有相同签名的默认方法的接口,编译器将强制您为 class 覆盖此方法。

例如使用这两个接口:

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

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

它不会编译:

public class FooBar implements Foo, Bar{
}

你应该define/override消除歧义的方法。
例如,您可以委托给 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 不允许多重继承,即使对于接口默认方法也是如此。


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

  • overriding/removing 两个继承的 classes 中方法的歧义可能会引入副作用并改变继承的 classes 的整体行为,如果它们在内部依赖于此方法。对于默认接口,这种风险也存在,但这种风险应该少得多,因为默认方法并非旨在引入复杂的处理,例如 class 内的多个内部调用或有状态的(实际上接口不能托管实例字段)。
  • 如何继承多个字段?即使语言允许,您也会遇到与之前引用的完全相同的问题:继承 class 行为的副作用:int foo 字段定义在 AB class 与你想要的class 没有相同的意思和意图。

我认为这主要与 "diamonds problem" 有关。现在,如果您使用相同的方法实现多个接口,编译器会强制您覆盖您想要实现的方法,因为它不知道要使用哪个。我猜 Java 创建者想在接口不能使用默认方法时解决这个问题。现在他们想出了一个主意,能够在接口中实现方法是件好事,因为您仍然可以将它们用作流/lambda 表达式中的功能接口,并在处理中使用它们的默认方法。你不能用 类 做到这一点,但钻石问题仍然存在。这是我的猜测:)

default 接口中的方法带来了一个问题:

If both of the implemented interfaces define a default method with same method signature, then the implementation class does not know which default method to use.

实现 class 应该明确指定要使用的默认方法或定义它自己的方法。

因此 default Java-8 中的方法不利于多重继承。默认方法背后的主要动机是,如果在某个时候我们需要向现有接口添加方法,我们可以添加方法而不更改现有实现 classes。这样,界面仍然兼容旧版本。但是,我们应该记住使用默认方法的动机,应该保持接口和实现的分离。

语言设计者已经想到了这一点,所以这些事情是由编译器强制执行的。所以如果你定义:

interface First {
    default void go() {
    }
}

interface Second {
    default void go() {
    }
}

并且您为两个接口实现了 class:

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::go 已经为 Second::go 提供了一个实现,应该没问题。这太照顾了,因此也不编译。

JLS 9.4.1.3 : Similarly, when an abstract and a default method with matching signatures are inherited, we produce an error. In this case, it would be possible to give priority to one or the other - perhaps we would assume that the default method provides a reasonable implementation for the abstract method, too. But this is risky, since other than the coincidental name and signature, we have no reason to believe that the default method behaves consistently with the abstract method's contract - the default method may not have even existed when the subinterface was originally developed. It is safer in this situation to ask the user to actively assert that the default implementation is appropriate (via an overriding declaration).

我要提出的最后一点是,即使在 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
    }
}

但是如果我们为一个接口改变它(你可以实现多个接口,不像 classes):

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 : A class does not inherit static methods from its superinterfaces.

多重继承的主要问题是排序(用于覆盖和调用 super)、字段和构造函数;接口没有字段或构造函数,因此它们不会引起问题。

如果您查看其他语言,它们通常分为两大类:

  1. 具有多重继承的语言加上一些用于消除特殊情况歧义的功能:虚拟继承 [C++],直接调用最派生的 class [C++] 中的所有超构造函数,线性化superclasses [Python],super [Python] 等的复杂规则

  2. 具有不同概念的语言,通常称为interfacestraitsmixinsmodules 等施加了一些限制,例如:没有构造函数 [Java] 或没有带参数的构造函数 [Scala 直到最近],没有可变字段 [ Java]、覆盖的具体规则(例如 mixins 优先于基础 classes [Ruby],所以当你需要一堆实用方法时你可以包含它们),等等 Java变成了这样的语言。

为什么仅仅通过禁止字段和构造函数就可以解决与多重继承相关的许多问题?

  • 在重复的基础 class 中不能有重复的字段。
    • 主要 class 层次结构仍然是线性的。
  • 您不能以错误的方式构造基础对象。
    • 想象一下,如果 Object 有 public/protected 字段,并且所有子 classes 都有构造函数设置这些字段。当您继承多个 class(它们都派生自 Object)时,哪一个可以设置字段?最后一个class?他们成为等级制度中的兄弟姐妹,因此他们对彼此一无所知。您是否应该拥有 Object 的多个副本以避免这种情况?所有 classes 都能正确互操作吗?
  • 请记住,Java 中的字段不是虚拟的(可覆盖的),它们只是数据存储。
    • 你可以创建一种语言,其中字段的行为类似于方法并且可以被覆盖(实际存储将始终是私有的),但这将是一个更大的变化,可能不会再被称为 Java .
  • 接口不能自己实例化。
    • 您应该始终将它们与具体的 class 结合起来。这消除了对构造函数的需要,并使程序员的意图也更加清晰(即,什么是具体的 class 什么是附属的 interface/mixin)。这也提供了一个定义明确的地方来解决所有歧义:具体 class.

JAVA 支持多重继承。 如果您对编程语言进行全面比较,JAVA,那么您就会知道我是对的。

Java 的顶部class 或 Ancestor Hierarchy 中的根 class 是对象 class。 这个 class 是所有其他 class 的超级 class。因此,我们声明的或在 API 中预定义的 Java 中的每个 class 都继承了此对象 class.

此外,Java 为我们提供了继承一个我们选择的 class。

因此,我们可以说我们正在执行互锁但多重继承。

二次元

Java 支持接口的多重继承。因此,您可以使用任意数量的接口实现。但请注意,实现一个接口并没有定义 IS 关系,因为 类 的继承是可能的。