Consider the following code:
public class A {
public static void main(String[] args) {
Runnable test1 = ((I)(new I() {}))::test; // compiles OK
Invoking a private
method is only possible through an expression of exactly the declaring type, regardless of the scenario.
Let’s explain it with the simplest example
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 {
}
You might expect this to compile using b.someMethod()
to invoke A
’s someMethod()
. But what if B
was declared as
class B extends A {
public void someMethod() {}
}
This is possible, as the private void someMethod()
is not inherited, so public void someMethod()
does not override it. But it should be clear that now b.someMethod()
should invoke B
’s method.
So if it was allowed that b.someMethod()
ends up at a private
method of A
, it would depend on whether B
declares another someMethod()
, at which actual method the call will end up. And that obviously contradicts the entire concept of private
methods. private
methods are not inherited and never overridden, so it should not depend on the subclass, whether a call ends up at a private
method or a subclass’ method.
Your example is similar. The anonymous inner class that implements I
could declare its own test()
method, e.g. Runnable test2 = ((new I() {void test() {}}))::test;
so it would depend on that anonymous inner class, whether the private
method of I
or a method of that anonymous inner class gets invoked, which would be unacceptable. Of course, with such an inner class, directly preceding the invocation or method reference, a reader can immediately tell, at which method the invocation will end up, but it would be very inconsistent, if this was allowed for an anonymous inner class but nothing else.
The private
method of I
is accessible to A
as it is a the nested interface, but as shown with the simpler example above, the rule is not about accessibility, as the rule even applies when the private
method is within the same class as the caller.
private
methods are not inherited (Closest I found so far is: JLS6.6-5: "[A private class member] is not inherited by subclasses"). That means that you can not access a private method, from a subtype (because it simply does not 'have' that method). For instance:
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 {}
That also means that you can not directly access the test
method through the type of an anonymous subclass.
The type of the expression:
(new I() {})
Is not I
, but actually the non-denotable type of the anonymous subclass, so you can't access test
through it.
However, the type of the expression:
((I) (new I() {}))
is I
(as you explicitly cast it to I
), so you can access the test
method through it. (just like you can do ((I1) i2).test();
in my above example)
Similar rules apply to static
methods, as they are also not inherited.
This is counter-intuitive. First let's simplify this a bit:
static interface Inter {
private void test() {
System.out.println("test");
}
}
public static void main(String[] args) {
((Inter) new Inter() {
}).hashCode();
}
This makes sense as you are calling the public hashCode
method, here is the (important part only) byte code for it:
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
Looks very sane to me. Now let's change that to calling test()
:
public static void main(String[] args) {
((Inter) new Inter() {
}).test();
}
The byte code for this:
invokestatic #4 // InterfaceMethod com/test/DeleteMe$Inter.access$000:(Lcom/test/DeleteMe$Inter;)V
Since private methods are not inherited, you are actually "going" to that method via the access$n
static synthetic method.
This is not a new issue, and has nothing to do with private interface methods or method references.
If you change code to extend a class instead of implement an interface, and to call the method instead of referencing it, you still get exact same problem.
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() {}
}
}
However, that code can be applied to older Java versions, and I tried Java 9, 8, 7, 6, 5, and 1.4. All behave the same!!
The issue is that private methods are not inherited1, so the anonymous class doesn't have the method, at all. Since the private method doesn't even exist in the anonymous class, it cannot be called.
When you cast to I
, the method now exists for the compiler to see, and since I
is an inner class, you are granted access (through a synthetic method), even though it is private.
In my opinion, it is not a bug. It's how private methods work in context of inheritance.
1) As found by Jorn Vernee in JLS 6.6-5: "[A private class member] is not inherited by subclasses".