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

时间:2018-10-03 06:29:34

标签: java inheritance interface java-8 abstract

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

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

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

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

7 个答案:

答案 0 :(得分:36)

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

例如,这两个接口:

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 fooA中定义的B字段您要子类化的类具有不同的含义和意图。

答案 1 :(得分:25)

语言设计者已经考虑过这一点,因此这些事情由编译器强制执行。因此,如果您定义:

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:类似地,当继承具有匹配签名的抽象方法和默认方法时,我们会产生一个错误。在这种情况下,可以将一个优先级赋予另一个优先级-也许我们会假设默认方法也为抽象方法提供了合理的实现。但这是有风险的,因为除了巧合的名称和签名之外,我们没有理由相信默认方法的行为与抽象方法的约定一致-在最初开发子接口时,默认方法甚至可能不存在< / strong>。在这种情况下,要求用户主动断言默认实现是适当的(通过覆盖声明),这样比较安全。

我要介绍的最后一点是,要确定即使在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:类不会从其超级接口继承静态方法。

答案 2 :(得分:13)

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

在Oracle / Openjdk中,对象具有标头,其后是最上级类的字段,然后是下一个最上级类的字段,依此类推。这将是一个重大变化,允许类的字段以相对于彼此的偏移量出现到不同子类的对象的标题。要支持此操作,最有可能的对象引用必须成为对对象标头的引用和对字段的引用。

答案 3 :(得分:4)

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

答案 4 :(得分:4)

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

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

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

  2. 具有不同概念的语言,通常称为 interfaces traits mixins modules ,等等施加了一些限制,例如:没有构造函数[Java]或没有带参数的构造函数[Scala直到最近],没有可变字段[Java],特定的覆盖规则(例如,mixins优先于基类[Ruby],所以您可以在需要大量实用程序方法时将它们包括在内)等。Java已成为此类语言。

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

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

答案 5 :(得分:3)

接口中的

default方法带来了一个问题:

  

如果两个已实现的接口都使用   相同的方法签名,则实现类不知道   使用哪种默认方法。

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

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

答案 6 :(得分:0)

Java 支持多重继承。 如果您对编程语言、Java 进行总体比较,您就会知道我是对的。

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

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

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

第二种方式

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