私有接口方法的方法参考

时间:2018-01-11 20:27:47

标签: java language-lawyer java-9

请考虑以下代码:

public class A {
    public static void main(String[] args) {
        Runnable test1 = ((I)(new I() {}))::test;  // compiles OK
        Runnable test2 = ((new I() {}))::test;     // won't compile 
    }

    interface I {
        private void test() {}
    }
}

我真的不明白......我知道test()方法私有。但是,如果我们将匿名类强制转换为其接口((I)(new I() {})),会发生什么变化?更准确地说,我希望看到一个允许这种技巧的特定JLS点。

P.S。我已将其报告为编译器错误(ID:9052217)。在我看来,Runnable test2 = ((new I() {}))::test;应该在这种特殊情况下编译好。

P.P.S。到目前为止,基于我的报告创建了一个错误:https://bugs.openjdk.java.net/browse/JDK-8194998。它可能会被关闭,因为"不会修复"或者无论如何。

4 个答案:

答案 0 :(得分:28)

这不是一个新问题,与私有接口方法或方法引用无关。

如果更改代码以扩展类而不是实现接口,并调用方法而不是引用它,则仍会遇到完全相同的问题。

class A {
    public static void main(String[] args) {
        ((I)(new I() {})).test();  // compiles OK
        ((new I() {})).test();     // won't compile 
    }

    class I {
        private void test() {}
    }
}

但是,该代码可以应用于较旧的Java版本,我尝试了Java 9,8,7,6,5和1.4。一切都表现得一样!!

问题是私有方法不是继承的 1 ,所以匿名类根本没有这个方法。由于私有方法甚至不存在于匿名类中,因此无法调用它。

当你转换为I时,现在存在让编译器看到的方法,并且由于I是一个内部类,你被授予访问权限(通过合成方法),即使它是私有的。

在我看来,这不是一个错误。它是私有方法在继承环境中的工作方式。

1)found by Jorn Vernee中的JLS 6.6-5" [私有类成员]不会被子类继承"

答案 1 :(得分:20)

private方法不会被继承(到目前为止我找到的最近的是:JLS6.6-5“[私有类成员]不是由子类”继承的。这意味着您无法从子类型访问私有方法(因为它根本没有“拥有”该方法)。例如:

public static void main(String[] args) {
    I1 i1 = null;
    I2 i2 = null;

    i1.test(); // works
    i2.test(); // method test is undefined
}

interface I1 {
    private void test() {}
}

interface I2 extends I1 {}

这也意味着您无法通过匿名子类的类型直接访问test方法。 表达式的类型:

(new I() {})

不是I,而是匿名子类的不可表示类型,因此您无法通过它访问test

然而,表达式的类型:

((I) (new I() {}))

I(当您明确地将其转换为I时),因此可以通过它访问test方法。 (就像你在上面的例子中可以做((I1) i2).test();

类似的规则适用于static方法,因为它们也不会被继承。

答案 2 :(得分:6)

这是违反直觉的。首先让我们简化一下:

static interface Inter {
    private void test() {
        System.out.println("test");
    }
}


public static void main(String[] args) {
    ((Inter) new Inter() {
    }).hashCode();
}

这是有意义的,因为您调用公共hashCode方法,这里是它的(仅限重要部分)字节代码:

public static void main(java.lang.String[]);
Code:
   0: new           #2   // class com/test/DeleteMe$1
   3: dup
   4: invokespecial #3   // Method com/test/DeleteMe$1."<init>":()V
   7: invokevirtual #4   // Method java/lang/Object.hashCode:()I
  10: pop
  11: return

对我来说看起来非常理智。现在让我们将其更改为调用test()

public static void main(String[] args) {
    ((Inter) new Inter() {
    }).test();
}

此字节代码:

 invokestatic  #4  // InterfaceMethod com/test/DeleteMe$Inter.access$000:(Lcom/test/DeleteMe$Inter;)V

由于私有方法不是继承的,所以你实际上是&#34;去&#34;通过access$n静态合成方法到该方法。

答案 3 :(得分:6)

只有通过完全声明类型的表达式才能调用private方法,无论情况如何。

让我们用最简单的例子来解释它

public class A {
    public static void main(String[] args) {
        B b = new B();
        b.someMethod(); // does not compile
        A a = b;
        a.someMethod(); // no problem
    }
    private void someMethod() {}
}
class B extends A {
}

您可能希望使用b.someMethod()进行编译以调用A的{​​{1}}。但如果someMethod()被声明为

,该怎么办?
B

这是可能的,因为class B extends A { public void someMethod() {} } 未被继承,因此private void someMethod()不会覆盖它。但应该很清楚,现在public void someMethod()应该调用b.someMethod()的方法。

因此,如果允许Bb.someMethod() private方法结束,则取决于A是否声明另一个B,调用将以哪种实际方法结束。这显然与someMethod()方法的整个概念相矛盾。 private方法不会被继承并且永远不会被覆盖,因此它不应该依赖于子类,调用是以private方法还是子类方法结束。

你的例子很相似。实现private的匿名内部类可以声明自己的I方法,例如test()因此它依赖于匿名内部类,无论Runnable test2 = ((new I() {void test() {}}))::test;的{​​{1}}方法还是匿名内部类的方法被调用,这都是不可接受的。当然,对于这样一个内部类,直接在调用或方法引用之前,读者可以立即告诉调用将以哪种方式结束,但如果允许匿名内部类,则它将非常不一致其他

private的{​​{1}}方法可供I访问,因为它是嵌套的界面,但如上面更简单的示例所示,该规则与可访问性无关,因为当private方法与调用者属于同一类时,规则甚至适用。