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



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

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

interface TestInterface 
{ 
// abstract method 
public void square(int a); 
// default method 
default void show() 
{ 
System.out.println("Default Method Executed"); 
} 
} 

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

例如,使用这两个接口:

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();
}    
}

或委托给CCD_ 2实现,例如::

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

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

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

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


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

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

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

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::go已经为Second::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:类不会从其超接口继承静态方法。

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

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

default方法在接口中提出了一个问题:

如果两个实现的接口都定义了一个默认方法相同的方法签名,则实现类不知道要使用的默认方法。

实现类应该显式定义指定要使用的默认方法或定义自己的方法。

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

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

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

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

  2. 具有不同概念的语言,通常称为接口特征混合项模块等,这些语言施加了一些限制,例如:没有构造函数[Java]或没有带参数的构造函数[Scala直到最近],没有可变字段[Java],重写的特定规则(例如,mixin优先于基类[Ruby],所以当你需要一堆实用程序方法时,你可以包括它们)等等。Java已经成为这样一种语言。

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

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

我认为这主要与"钻石问题"有关。现在,若您用同一个方法实现多个接口,编译器会强制您重写要实现的方法,因为它不知道在哪个方法上使用。我想Java创建者希望在接口不能使用默认方法时消除这个问题。现在他们提出了这样的想法,即能够在接口中实现方法是很好的,因为您仍然可以在流/lambda表达式中使用这些方法作为函数接口,并在处理中使用它们的默认方法。类不能做到这一点,但钻石问题仍然存在。这是我的猜测:)

class A{
void m1(){
System.out.println("m1-A");
}
}
class B{
void m1(){
System.out.println("m1-B");
}
}
class C extends A, B{    // this will give an error
// inheritance means making all variables and/or methods available to the child class, here child class will get confused as which m1() method to inherit, hence an error
}
JAVA确实支持多重继承。如果你对编程语言JAVA进行全面的比较,那么你就会知道我是真的。

Ancestor层次结构中Java的顶级或根类是Object类。这个类是所有其他类中的一个超类。因此,我们在API中声明或预定义的Java中的每个类本身都继承了这个Object类。

此外,Java为我们提供了继承我们选择的另一个类的功能。

因此,我们可以说,我们正在执行联锁但多重遗传。

2路

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

最新更新