【问题标题】:static method in class have same signature as default method in interface类中的静态方法与接口中的默认方法具有相同的签名
【发布时间】:2018-01-16 20:10:29
【问题描述】:

我有以下情况:

class C {
    static void m1() {}
}

interface I {
    default void m1() {}
}

//this will give compilation error : inherited method from C cannot hide public abstract method in I
class Main extends C implements I {

}

以下是我的问题:

  1. 我知道实例方法会覆盖默认方法,但是如果类中的静态方法与接口中的默认方法具有相同的签名怎么办?

  2. 如果class C 中的静态方法m1() 是公开的,那么编译错误将是:

    静态方法 m1() 与 I 中的抽象方法冲突。

因此,当访问修饰符为默认时,它试图隐藏,而当它是公共的时,它是冲突的。为什么会有这种差异?它背后的概念是什么?

【问题讨论】:

  • 你使用的是哪个编译器?
  • 这不应该依赖于默认实现或访问级别的存在——在我看来,编译错误应该是相同的。 eclipse中一直是“This static method cannot hide the instance method from I”,用oracle jdk 1.8.0_121 "[path]/A.java:[5,17] m1() in [path].A无法实现[path] 中的 m1().I 覆盖方法是静态的“
  • 我也在使用 1.8,它显示不同的编译错误

标签: java inheritance multiple-inheritance default-method


【解决方案1】:

归根结底,当你有这样的事情时:

class Me {
    public static void go() {
        System.out.println("going");
    }
}

这两个都是允许的:

Me.go();
Me meAgain = new Me();
meAgain.go(); // with a warning here

有趣的是,这也可以工作,例如:

Me meAgain = null;
meAgain.go();

我个人仍然认为这是由于兼容性而无法收回的设计缺陷 - 但我希望编译器不允许我从实例访问静态方法。

您的第一个问题与 java-8 本身无关,在 java-8 之前一直是这样的:

interface ITest {
    public void go();
}

class Test implements ITest {
    public static void go() { // fails to compile

    }
}

默认方法在这里遵循相同的规则。为什么会发生这种情况实际上在堆栈溢出方面有很多详细说明 - 但基本思想是可能这会导致混淆调用哪个方法(想象ITest 将是Test 的类扩展,你做ITest test = new Test(); test.go(); -> 你在调用哪个方法?)

我认为出于同样的原因,这也是不允许的(这基本上是你的第二个问题,否则你会有一个具有相同签名的静态和非静态方法)

static class Me {
    static void go() {

    }

    void go() {

    }
}

有趣的是,这在方法引用中是固定的(我猜他们意识到再次犯同样的错误会很糟糕):

static class Mapper {
    static int increment(int x) {
        return x + 1;
    }

    int decrement(int x) {
        return x - 1;
    }
}


Mapper m = new Mapper();
IntStream.of(1, 2, 3).map(m::increment); // will not compile
IntStream.of(1, 2, 3).map(m::decrement); // will compile

【讨论】:

    【解决方案2】:

    回答你的第一个问题:

    “类中的静态方法”和“接口中的默认方法”都可用于类Main,因此如果它们具有相同的签名,则会产生歧义。

    例如:

    class C{
        static void m1(){System.out.println("m1 from C");}
    }
    
    public class Main extends C{
        public static void main(String[] args) {
            Main main=new Main();
            main.m1();
        }
    }
    

    输出:m1 from C

    同样,

    interface I{
        default void m1(){System.out.println("m1 from I");}
    }
    
    public class Main implements I{
        public static void main(String[] args) {
            Main main=new Main();
            main.m1();
        }
    }
    

    输出:m1 from I

    如您所见,两者都可以类似地访问。所以这也是实现I和扩展C时发生冲突的原因。

    回答你的第二个问题:

    如果您的类和接口在同一个包中,则默认和公共访问修饰符应该类似地工作。

    另外,C 中的m1() 是静态的,不能被覆盖,因此它不能被认为是I 中的m1() 的实现,因此存在编译问题。

    希望有帮助!

    【讨论】:

    • 类和接口在同一个包中,但行为仍然不同
    • 如果有什么比你提到的更多
    【解决方案3】:

    我会回答你的第一个问题,因为第二个问题已经回答了

    我知道实例方法会覆盖默认方法,但是 如果类中的静态方法与默认方法具有相同的签名怎么办 在界面中?

    我假设您使用的是 JDK 1.8,因此会造成混淆。接口方法中的default 修饰符不是在谈论它的访问规范。相反,它提到接口本身需要实现此方法。该方法的访问规范仍然是公开的。从 JDK8 开始,接口允许您使用默认修饰符指定方法,以允许以向后兼容的方式扩展接口。

    在您的界面中,您必须提供default void m1() {} 才能使编译成功。通常我们只是以抽象的方式定义它们,例如在接口中void m1(); 您必须实现该方法,因为您将方法指定为默认值。希望你能理解。

    【讨论】:

    • "从 JDK8 开始,接口允许您使用默认修饰符指定方法" - 这是什么意思?接口中的方法总是public(即使没有给出访问修饰符)
    • 如果您完整阅读答案,您将理解我试图传达的内容。如果你用 default 修饰符定义了一个方法,编译器会强制你在接口本身中实现这个方法。我以为 OP 错过了这个功能,因为这是新功能。
    • 如果你觉得我说得不对或者你可以做得更好,请随意
    • 不,抱歉,这很好(我不应该在凌晨 3:00 写 cmets...)。您只是提到了docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html,而不是默认可见性。
    【解决方案4】:

    因为java中的类方法也可以使用实例变量来调用,所以这种构造会导致歧义:

    Main m = new Main();
    
    m.m1();
    

    不清楚最后一条语句应该调用类方法C.m1()还是实例方法I.m1()

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-05-05
      • 2023-03-26
      • 1970-01-01
      • 1970-01-01
      • 2014-10-04
      • 2014-05-06
      • 2021-06-02
      • 2015-03-06
      相关资源
      最近更新 更多