私有接口方法的方法参考
请考虑以下代码:
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点。
PS我已将其报告为编译器的错误(ID:9052217)。 在我看来, Runnable test2 = ((new I() {}))::test;
应该在这种特殊情况下编译好。
PPS到目前为止,根据我的报告创建了一个错误: https : //bugs.openjdk.java.net/browse/JDK-8194998 。 它可能会被关闭为“无法修复”或无论如何。
这不是一个新问题,与私有接口方法或方法引用无关。
如果您更改代码以扩展类而不是实现接口,并调用方法而不是引用它,您仍然会得到完全相同的问题。
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。 一切都表现得一样!!
问题是私有方法没有被inheritance1 ,所以匿名类根本就没有这个方法。 由于私有方法甚至不存在于匿名类中,因此无法调用它。
当你转换为I
,现在存在供编译器查看的方法,并且因为I
是一个内部类,所以即使它是私有的,你也被授予访问权限(通过合成方法)。
在我看来,这不是一个错误。 这是私有方法在inheritance的上下文中的工作方式。
1) 由Jorn Vernee在JLS 6.6-5中 发现 : “[私有类成员]不是由子类inheritance的” 。
private
方法不是inheritance的(到目前为止我发现的最近的是: JLS6.6-5 : “[私有类成员]不是由子类inheritance的” )。 这意味着您无法从子类型访问私有方法(因为它根本没有“拥有”该方法)。 例如:
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
方法,因为它们也不是inheritance的。
无论方案如何,只能通过完全声明类型的表达式来调用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()
不会被inheritance,所以public void someMethod()
不会覆盖它。 但应该很清楚,现在b.someMethod()
应该调用B
的方法。
因此,如果允许b.someMethod()
以A
的private
方法结束,那么它将取决于B
是否声明另一个someMethod()
,在该实际方法中调用将结束。 这显然与private
方法的整个概念相矛盾。 private
方法不会被inheritance并且永远不会被覆盖,因此它不应该依赖于子类,无论调用是以private
方法还是子类方法结束。
你的例子很相似。 实现I
的匿名内部类可以声明它自己的test()
方法,例如Runnable test2 = ((new I() {void test() {}}))::test;
所以它将依赖于匿名内部类,无论是I
的private
方法还是匿名内部类的方法都被调用,这是不可接受的。 当然,对于这样的内部类,直接在调用或方法引用之前,读者可以立即告诉调用将以哪种方式结束,但如果允许匿名内部类,则它将非常不一致其他。
A的private
方法可由A
访问,因为它是嵌套接口,但如上面更简单的示例所示,规则与可访问性无关,因为规则甚至适用于private
方法与调用方位于同一类中的情况。
这是违反直觉的。 首先让我们简化一下:
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."":()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
由于私有方法不是inheritance的,因此您实际上是通过access$n
静态合成方法“前进”到该方法。