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();
}
这在调用publichashCode
方法时很有意义,下面是它的字节码(仅限重要部分):
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(){}))::test因此,它将取决于匿名内部类,无论是调用I
的private
方法还是调用该匿名内部类的方法,这都是不可接受的。当然,对于这样一个内部类,直接在调用或方法引用之前,读者可以立即知道调用将在哪个方法结束,但是如果只允许匿名内部类这样做,而不允许其他方法,则会非常不一致
I
的private
方法可以被A
访问,因为它是嵌套接口中的一个类,但正如上面简单的示例所示,该规则与可访问性无关,因为当private
方法与调用方在同一个类中时,该规则甚至适用。Nice JLS参考+1.在我们之间(请参阅),我认为我们已经讨论了这个问题。到目前为止,根据我的报告创建了一个bug:。可能是因为“无法修复”或其他原因而关闭它。@Andremoniy为什么要关闭它?接口中的私有方法是供接口使用的,而不是供其他人使用。通过创建内部接口来绕过该规则,这样顶级类就可以访问,这确实是一个技巧,在我看来是一个糟糕的代码。我想我理解发生了什么,但是,我在这里发现了一个矛盾:因为私有方法甚至不存在于匿名类中,所以当您转换到I时,它不能被调用为VS,编译器现在可以看到该方法。无论是否使用强制转换,实例的实际类型都是不可表示的匿名类型。因此,如果方法被执行,那么它就在那里。@Federicoperaltachaffner想象你是编译器,是程序