类中的静态方法与接口中的默认方法具有相同的签名。

12

我有以下场景:

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. 如果C类中的静态方法m1()是公共的,则编译错误将为:

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

因此,当访问修饰符是默认时,它正在尝试隐藏;而当它是公共的时,它会发生冲突,这是为什么?背后的概念是什么?


你使用的编译器是哪个? - Hulk
1
这不应该依赖于默认实现或访问级别的存在 - 在我看来,编译错误应该是相同的。在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 cannot implement m1() in [path].I overriding method is static"。 - Hulk
我也在使用1.8版本,它显示了不同的编译错误。 - user2185089
4个回答

5

最终归结为这样一个事实,当你有像这样的东西:

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

    }
}

默认方法在这里也遵循相同的规则。为什么会这样发生,实际上在stackoverflow上已经详细解释了很多 - 但其基本思想是潜在地会导致调用哪个方法的混淆(想象一下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

1

回答您的第一个问题:

如果“类中的静态方法”和“接口中的默认方法”都对类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();
    }
}

输出:C中的m1

同样地,

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

输出:I中的m1

正如您所看到的,这两者可以类似地访问。这也是在实现I并扩展C时发生冲突的原因。

回答您的第二个问题:

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

此外,C中的m1()是静态的,不能被覆盖,因此它不能被视为Im1()的实现,因此会出现编译问题。

希望这能帮到您!


类和接口在同一个包中,但行为仍然不同。 - user2185089
如果有任何你没有提到的内容,请告诉我。 - OutOfMind

1

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

我知道实例方法会覆盖默认方法,但是如果类中的静态方法和接口中的默认方法具有相同的签名呢?

我假设您正在使用JDK 1.8,因此存在混淆。接口方法中的default修饰符并不涉及其访问规范。它表示接口本身需要实现此方法。该方法的访问规范仍然是public。从JDK8开始,接口允许您指定带有默认修饰符的方法,以便以向后兼容的方式扩展接口。

在您的接口中,您必须给出default void m1() {}才能使编译成功。通常我们在接口中以抽象方式定义它们,例如void m1();。由于您将方法指定为默认值,因此必须实现该方法。希望您明白。


从JDK8开始,接口允许您指定具有默认修饰符的方法。这是关于什么的?接口中的方法始终是public(即使没有给出访问修饰符)。 - Marco13
如果您完整阅读答案,您将理解我试图传达的内容。如果您使用默认修饰符定义一个方法,编译器将强制您在接口本身中实现该方法。我认为 OP 错过了这个新功能。 - Fayaz
如果您觉得我表达不够准确或者您能够更好地表达,请随意修改。 - Fayaz
不好意思,那很好(我不应该在凌晨3点写注释...)。你只是参考了https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html,而不是默认可见性。 - Marco13

0
因为 Java 中的类方法也可以使用实例变量进行调用,所以这种结构会导致歧义:
Main m = new Main();

m.m1();

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接