【问题标题】:Default interface method for abstract superclass抽象超类的默认接口方法
【发布时间】:2017-10-12 02:48:48
【问题描述】:

假设我有以下结构:

abstract class A {
     abstract boolean foo();
}

interface B {
     default boolean foo() { return doBlah(); }
}

class C extends A implements B {
    //function foo
}

Java 现在会抱怨 C 类必须从 A 实现抽象方法 foo。 我可以通过重新定义C 中的函数并简单地调用B.super.foo(); 来相对轻松地解决这个问题。

但是我不明白为什么接口 B 的默认函数不能自行满足这个要求,我想更好地了解 java 的底层机制。

【问题讨论】:

  • 你能给出一些应用上下文来解释这个结构吗?例如,为什么类A 不实现接口B?那么C 类应该只需要扩展A 类即可从这两种超类型中受益。
  • 这是正确的问题,我还没有看到正确的答案。当我们继承时,超类中的公共方法与子类中定义的方法一样好。所以我们可以想象 foo() 方法只在 C 类中实现。那么为什么编译器会抱怨呢?

标签: java java-8 default-method


【解决方案1】:

接口中的默认方法用于防止在向接口添加方法时破坏依赖于接口的程序。

在您描述的情况下,不清楚接口中的默认方法(根据设计必须在稍后添加)是否真正履行了抽象类最初设想的合同。

在这种情况下,Java 抱怨更安全。

以上文字是我对9.4.1.3 of the JLS SE8 spec中的段落的解释,我引用:

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

【讨论】:

    【解决方案2】:

    在程序的当前状态下,如果您要在C.java 中覆盖A#foo(默认方法返回true,而被覆盖的方法返回false,打印C#foo 将导致false,完全忽略默认方法。

    抽象类中定义的任何抽象方法都需要被扩展抽象类的第一个具体类覆盖。因此,C.java 需要覆盖 A#foo

    接口中的默认方法不需要被覆盖。

    但是,两种方法共享相同的签名,这意味着需要覆盖一个,而另一个可能被覆盖。

    这是非常糟糕的设计,不应该使用,因为共享相同签名的方法不能同时被覆盖。如果您希望覆盖抽象方法,只需将抽象方法或默认方法的名称更改为 foo 以外的名称。

    abstract class A {
        abstract boolean bar();
    }
    
    interface B {
        default boolean foo() { return doBlah(); }
    }
    
    class C extends A implements B {
        @Override
        public boolean foo() {
            ...
        }   
    
        @Override
        public boolean bar() {
            ...
        }
    }
    

    如果您只想在某些情况下覆盖A#foo,那么您可以简单地完全删除抽象方法并在B.java 中保留默认方法,然后在C.java 中覆盖它:

    abstract class A {
    
    }
    
    interface B {
        default boolean foo() { return doBlah(); }
    }
    
    class C extends A implements B {
        @Override
        public boolean foo() {
            ...
        }   
    }
    

    如果删除 A#foo 不是一个选项,则将默认方法 B#foo 重命名为其他名称。

    abstract class A {
        abstract boolean foo();
    }
    
    interface B {
        default boolean bar() { return doBlah(); }
    }
    
    class C extends A implements B {
        @Override
        public boolean foo() {
            ...
        }   
    
        @Override
        public boolean bar() {
            ...
        }
    }
    

    【讨论】:

    • 很好的解释,但完全删除 A#foo 不是一种选择,因为它显然意味着您不能再在您的 A 集合上调用 foo。但是,我想我也找到了一个可能的解决方案,我会在答案中写出来,这样会更清楚。
    • 如果删除 A#foo 不是一个选项,则将默认方法 B#foo 重命名为其他名称。
    【解决方案3】:

    Jacob G. 的回答启发了我想出这个解决方案:

    interface Z {
         abstract boolean foo();
    }
    
    abstract class A implements Z {
    
    }
    
    interface B extends Z {
         default boolean foo() { return doBlah(); }
    }
    
    class C extends A implements B {
    
    }
    

    这样A类的所有子类都需要定义一个方法foo(),而不需要每个实现B的类都重新实现它。

    【讨论】:

    • A仍然存在,因为它包含其他无法在接口中实现的方法和变量。
    猜你喜欢
    • 2015-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多