Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Method reference to private interface method

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.

like image 629
Andremoniy Avatar asked Jan 11 '18 20:01

Andremoniy


People also ask

Can a method be private in interface?

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.

What is method reference?

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.


3 Answers

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".

like image 90
Andreas Avatar answered Oct 09 '22 18:10

Andreas


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.

like image 21
Jorn Vernee Avatar answered Oct 09 '22 19:10

Jorn Vernee


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.

like image 7
Holger Avatar answered Oct 09 '22 19:10

Holger