有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

我不是在问这个->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"); 
    } 
} 

共 (4) 个答案

  1. # 1 楼答案

    语言设计者已经考虑过了,所以这些都是由编译器来实现的。所以如果你定义:

    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 : 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
        }
    }
    

    但是,如果我们对一个接口进行更改(并且可以实现多个接口,而不是类):

    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.

  2. # 2 楼答案

    接口中的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.

    实现类应该明确定义使用哪个默认方法,或者定义自己的方法

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

  3. # 3 楼答案

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

    例如,这两个接口:

    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也不允许多重继承


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

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

    多重继承的主要问题是排序(用于重写和调用super)、字段和构造函数;接口没有字段或构造函数,因此不会导致问题

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

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

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

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

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