Java 方法引用到私有接口方法

Java 方法引用到私有接口方法,java,language-lawyer,java-9,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

考虑以下代码:

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()
方法是private。但是,如果我们将一个匿名类强制转换到它的接口
((I)(newi(){}))
,会发生什么变化呢?更确切地说,我希望看到一个特定的JLS点,它允许这种技巧

p.S.我报告它是编译器的一个bug(ID:9052217)。在我看来,
Runnable test2=((新的I(){}))::test


p.p.S.到目前为止,根据我的报告创建了一个bug:。它可能会被关闭为“不会修复”或诸如此类。

private
方法不会被继承(到目前为止我发现的最接近的方法是::“[私有类成员]不会被子类继承”)。这意味着您不能从子类型访问私有方法(因为它根本没有该方法)。例如:

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) (new I() {}))
不是
I
,而是匿名子类的不可表示类型,因此不能通过它访问
test

但是,表达式的类型:

(new I() {})
((I) (new I() {}))
I
(当您显式地将其强制转换为
I
),因此您可以通过它访问
test
方法。(就像我上面的例子中的
((I1)i2.test();
)一样)


类似的规则适用于
静态方法,因为它们也不是继承的。

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

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

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版本,我尝试了Java9、8、7、6、5和1.4。所有人的行为都一样

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

当您强制转换到
I
时,编译器现在可以看到该方法,并且由于
I
是一个内部类,因此您可以被授予访问权限(通过合成方法),即使它是私有的

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


1) 例如:“[私有类成员]不是由子类继承的。”

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

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


public static void main(String[] args) {
    ((Inter) new Inter() {
    }).hashCode();
}
这在调用public
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
此文件的字节码:

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

由于私有方法不是继承的,因此实际上您是通过
access$n
静态合成方法“转到”该方法。

调用
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
someMethod()。但是如果
B
被声明为

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

因此,如果允许
b.someMethod()
a
private
方法结束,这将取决于
b
是否声明另一个
someMethod()
,调用将在哪个实际方法结束。这显然与
private
方法的整个概念相矛盾
private
方法不会被继承,也不会被重写,因此它不应该依赖于子类,不管调用是在
private
方法还是子类方法结束

你的例子很相似。实现
I
的匿名内部类可以声明自己的
test()
方法,例如
Runnable test2=((新的I(){void test(){}))::testI
private
方法还是调用该匿名内部类的方法,这都是不可接受的。当然,对于这样一个内部类,直接在调用或方法引用之前,读者可以立即知道调用将在哪个方法结束,但是如果只允许匿名内部类这样做,而不允许其他方法,则会非常不一致


I
private
方法可以被
A
访问,因为它是嵌套接口中的一个类,但正如上面简单的示例所示,该规则与可访问性无关,因为当
private
方法与调用方在同一个类中时,该规则甚至适用。

Nice JLS参考+1.在我们之间(请参阅),我认为我们已经讨论了这个问题。到目前为止,根据我的报告创建了一个bug:。可能是因为“无法修复”或其他原因而关闭它。@Andremoniy为什么要关闭它?接口中的私有方法是供接口使用的,而不是供其他人使用。通过创建内部接口来绕过该规则,这样顶级类就可以访问,这确实是一个技巧,在我看来是一个糟糕的代码。我想我理解发生了什么,但是,我在这里发现了一个矛盾:因为私有方法甚至不存在于匿名类中,所以当您转换到I时,它不能被调用为VS,编译器现在可以看到该方法。无论是否使用强制转换,实例的实际类型都是不可表示的匿名类型。因此,如果方法被执行,那么它就在那里。@Federicoperaltachaffner想象你是编译器,是程序