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.

4 answers

  • answered 2018-01-11 21:14 Jorn Vernee

    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.

  • answered 2018-01-11 21:14 Andreas

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

  • answered 2018-01-11 21:19 Eugene

    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: invokestatic  #4                  // InterfaceMethod com/test/DeleteMe$Inter.access$000:(Lcom/test/DeleteMe$Inter;)V
      10: 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.

  • answered 2018-01-12 15:17 Holger

    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.