25

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.

Holger
  • 285,553
  • 42
  • 434
  • 765
Andremoniy
  • 34,031
  • 20
  • 135
  • 241
  • 7
    `((new I() {}))::test` what is this ugly code and where did it come from? – insidesin Jan 12 '18 at 00:41
  • @insidesin How would you prefer to do that? :) – Andremoniy Jan 12 '18 at 10:38
  • 2
    Your question’s title is misleadingly wrong. The “interface with private method” obviously is `I`, which is *neither*, a functional interface nor being used “as functional interface”. – Holger Jan 12 '18 at 14:53
  • Why? I perceive it as I would take a regular interface with a public method, then will deal with it as with a functional interface. Here is the same, but I deal with an interface containing private method. – Andremoniy Jan 12 '18 at 14:55
  • @JarrodRoberson, no one of these questions is about private methods in interfaces for java-9 – Andremoniy Jan 12 '18 at 15:28
  • they all have the correct answer regardless, `private` methods are not inherited` and what @Holger says is relevant as well. –  Jan 12 '18 at 15:30
  • 1
    @JarrodRoberson you can not just extrapolate these answers to the absolutely new feature as private methods in interfaces – Andremoniy Jan 12 '18 at 15:30
  • yes you can, if you make it a regular class it has the same problem, always has. This is nothing new in Java 9. –  Jan 12 '18 at 15:32
  • I didn't make it "a regular class". The question is that for being a functional interface, interface has to have public method. Here ```I``` doesn't have any public method. – Andremoniy Jan 12 '18 at 15:34
  • 2
    Suggestion for the title: “*method reference to private interface method*”. A method reference will be converted to an instance of a functional interface (at runtime), but that’s not the point of this function. The issue was the same if there were true function types in Java so that the method reference worked without a functional interface. – Holger Jan 12 '18 at 15:34
  • @Holger feel free to rename it, I am not arguing – Andremoniy Jan 12 '18 at 15:35
  • Just one thing, if https://bugs.openjdk.java.net/browse/JDK-8194998 gets to be fixed as it is, I return my title back. Until that it is fine :) – Andremoniy Jan 12 '18 at 15:36
  • @insidesin you could do that even in java-8 (assuming that `test` is a public method of the interface) – Andremoniy Jan 13 '18 at 09:28
  • My comment got deleted? Weird. Delete my first comment too then... – insidesin Jan 13 '18 at 13:14
  • @insidesin nobody deleted your comments – Andremoniy Jan 13 '18 at 13:29
  • @Andremoniy They're not showing up here. Where are they? – insidesin Jan 15 '18 at 01:38

4 Answers4

29

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

Andreas
  • 154,647
  • 11
  • 152
  • 247
  • Doesn't it break the rule about **functional interface**s? – Andremoniy Jan 11 '18 at 21:19
  • 4
    @Andremoniy Why would it? A private method in an interface is for the interface to use, no one else. Skirting that rule by making an inner interface, so the top-level class has access, is really a trick, and bad code in my opinion. – Andreas Jan 11 '18 at 21:22
  • 4
    I think I understand what's going on, however, I find a contradiction here: *Since the private method doesn't even exist in the anonymous class, it cannot be called* VS *When you cast to I, the method now exists for the compiler to see*. Either with cast or without, the actual type of the instance is a non-denotable anonymous type. So, if the method gets executed, then it is there. – fps Jan 11 '18 at 21:33
  • 5
    @FedericoPeraltaSchaffner Imagine you are the compiler, and the programmer asks to call `test` on a certain expression. You look up the type of the expression, open it's list of methods, and do not find `test` there (since it is not inherited). However, with the cast the type of the expression changes, and because of that the list of methods you get changes, and now you _can_ find `test` there. – Jorn Vernee Jan 11 '18 at 22:02
  • @JornVernee Thank you very much for your clarification, I understand that the method is always there, but the compiler *sees* it only when the instance is casted to the type that can access the method. – fps Jan 11 '18 at 22:13
  • 5
    @FedericoPeraltaSchaffner *Clarification:* The compiler *sees* it only when the type is where the method is declared, since the private method is invisible on all other types. *Access* to the method is a separate thing, i.e. if you moved the interface out, to be a top-level interface, then the compiler would still see it with cast, but you now wouldn't have access anymore. As far as JVM is concerned, you don't have access even when the interface is an inner type, so the compiler fakes access to the private method by creating a hidden synthetic non-private bridge method, and calls that instead. – Andreas Jan 11 '18 at 23:37
  • 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. – Andremoniy Jan 12 '18 at 14:00
  • 2
    One consequence of this rule, is, that it is impossible to invoke `private` methods on references of a type parameter. That’s so unintuitive that the compilers got it wrong for Java 5 (Eclipse even in Java 6) and still keep the wrong behavior when setting the source level to these old versions, for compatibility. – Holger Jan 12 '18 at 16:02
  • 1
    @Holger Why do you say the compilers got it wrong? Private methods are for the type itself to use, and no one else, except for outer types of an inner type, and then the JLS specifically says that private methods are not inherited, so even inner subtypes of an inner type don't have access. It's in the spec, so not a compiler error. Of course you can't invoke private method of a type parameter, you're not supposed to. That's what private means. – Andreas Jan 12 '18 at 17:03
  • @Andreas: exactly, but for Java 5 the compilers *accept* access to `private` members through type variables, but reject it when switching to Java 6 (or Java 7 in the case of Eclipse). This has nothing to do with outer/inner types, the same applies to generic methods inside the same class that declares the member. Of course, the newer behavior is the correct one. – Holger Jan 12 '18 at 17:07
  • @Holger Ok, I didn't get the part of your comment that inferred Java 5 compiler was wrong amd Java 6 compiler fixed it. I think that it is entirely intuitive that private methods can only be called by the type itself. I think it is less intuitive that outer type can access private members of an inner type, but I do get why they defined it to be valid. The JVM doesn't allow it either, which is why the compiler has to fake it using synthetic methods. I mean, private is exactly that: Private to the type itself. – Andreas Jan 12 '18 at 17:13
21

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.

Jorn Vernee
  • 31,735
  • 4
  • 76
  • 93
  • 4
    Nice JLS reference. +1. Between us (see [my answer](https://stackoverflow.com/a/48215895/5221149)), I think we covered this. – Andreas Jan 11 '18 at 21:17
  • 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. – Andremoniy Jan 12 '18 at 14:00
7

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.

Holger
  • 285,553
  • 42
  • 434
  • 765
6

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.

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 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. – Andremoniy Jan 12 '18 at 14:00