42

Consider below class hierarchy.

class ClassA {
    private void hello() {
        System.out.println("Hello from A");
    }
}

interface Myinterface {
    default void hello() {
        System.out.println("Hello from Interface");
    }
}

class ClassB extends ClassA implements Myinterface {

}

public class Test {
    public static void main(String[] args) {
        ClassB b = new ClassB();
        b.hello();
    }
}

Running the program will give following error :

Exception in thread "main" java.lang.IllegalAccessError: tried to access method com.testing.ClassA.hello()V from class com.testing.Test
at com.testing.Test.main(Test.java:23)
  1. This is all because I marked ClassA.hello as private.
  2. If I mark ClassA.hello as protected or remove the visibility modifier(i.e. making it default scope), then it shows a compiler error as : The inherited method ClassA.hello() cannot hide the public abstract method in Myinterface

However, as per exception stacktrace above, I get a runtime IllegalAccessError.

I couldn't get why this is not detected at compile time. Any clues ?

Shailesh Pratapwar
  • 4,054
  • 3
  • 35
  • 46
  • 1
    Why should it give a compile time error ?. Since you have declared the method private in the super class and default in the interface, you need not override it. Now in your case, in order to access the interface's default implementation, you will have to override the method in the class and write MyInterface.super.hello(); – Ashish Kumar Apr 02 '18 at 12:06
  • 3
    Expectation is that it should call default method as method in ClassA is private and its not visible. However, when I run the program, I get a runtime error which is about the access to ClassA.hello which shouldn't be the case. – Shailesh Pratapwar Apr 02 '18 at 12:09
  • to me that is a problem with resolution, at compile time, `B#hello` is treated as the one from interface, at runtime it's the one from the class, searching for the JLS for proof... – Eugene Apr 02 '18 at 12:19
  • 3
    well, the java doc states _Normally, this error is caught by the compiler; this error **can only occur at run time** if the definition of a class has incompatibly changed._ – Ousmane D. Apr 02 '18 at 12:37
  • Arguably ClassB extends ClassA implements MyInterface shouldn’t compile because of the conflict. On the other hand, not compiling would mean private implementation details of ClassA are exposed. Can’t see how a satisfactory solution to this can even exist. – cpp beginner Apr 02 '18 at 12:41
  • 2
    @cppbeginner Why should the private method ever take part in the method resolution for the interface? Adding a private method to a library should *never* introduce any risk imo. – Voo Apr 02 '18 at 16:12
  • 2
    @Voo Yes you may be right. I must admit having written that comment I’m now even more confused about what the issue actually is, especially as pointed out in the comments below the answer, this bug even exists for static methods. I can only assume that since this bug has been around for years there must be some fundamental reason why it’s really tricky to solve. – cpp beginner Apr 02 '18 at 16:54

1 Answers1

31

Update: Seems like it's really a bug.

A class or super-class method declaration always takes priority over a default method!

default hello(...) method from the Myinterface allows you to write without errors:

ClassB b = new ClassB();
b.hello();

Until runtime, because at runtime hello(...) method from the ClassA takes the highest priority (but the method is private). Therefore, IllegalAccessError occurs.

If you remove the default hello(...) method from the interface, you get the same illegal access error, but now at compile time.

Turing85
  • 18,217
  • 7
  • 33
  • 58
Oleksandr Pyrohov
  • 14,685
  • 6
  • 61
  • 90
  • 3
    oh good thing this is a bug! I could not really find anything very related in the JLS – Eugene Apr 02 '18 at 14:44
  • 1
    That `registerNatives` example in your link (which I was able to reproduce) is particularly weird as `registerNatives` is actually `static`. – Paul Boddington Apr 02 '18 at 15:15
  • @PaulBoddington, technically, static method resolution works very much the same here, because non-static methods are different from static only in part where `this` is passed to non-static methods as a first implicit argument. – M. Prokhorov Apr 03 '18 at 14:00
  • @M.Prokhorov interesting is that this `static class A { private static void ts() { } }` and `static class B extends A { public static void ts() { } }` with usage `A a = new A(); a.ts();` will work just fine... – Eugene Apr 10 '18 at 11:44
  • @Eugene, well, it's certainly a bug, that much is obvious by now. The fact that it would work "fine" is likely a consequence of requirement that default interface methods are to be excluded from `invokedynamic` resolution if any matching methods from concrete classes are found. – M. Prokhorov Apr 10 '18 at 11:52
  • @M.Prokhorov may be, i just find it very misleading and was *truly* hoping that the static method would fail too – Eugene Apr 10 '18 at 11:53
  • Wait, I read into the last example some more. Are you sure the usage is not `B a = new B(); b.ts();`? Class `A` is not supposed to have a visible static method `ts`, only `B` is. – M. Prokhorov Apr 10 '18 at 11:57