Consider the following code:
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() {}
}
}
I don't really get the point... I understand that test()
method is private. But what is changed if we cast an anonymous class to its interface ((I)(new I() {}))
? More precisely I would like to see a particular JLS point which allows that trick.
P.S. I have reported it as a bug of compiler (ID : 9052217). It seems to me that Runnable test2 = ((new I() {}))::test;
should be compiled fine in this particular case.
P.P.S. So far there was created a bug based on my report: https://bugs.openjdk.java.net/browse/JDK-8194998 . It might be that it will be closed as "won't fix" or whatsever.
An interface can have private methods since Java 9 version. These methods are visible only inside the class/interface, so it's recommended to use private methods for confidential code. That's the reason behind the addition of private methods in interfaces.
Method references are a special type of lambda expressions. They're often used to create simple lambda expressions by referencing existing methods. There are four kinds of method references: Static methods. Instance methods of particular objects. Instance methods of an arbitrary object of a particular type.
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".
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With