IllegalAccessError во время работы над наследованием - Почему?

Я нашел эту ошибку JDK и хочу понять, почему это происходит.

Сценарий (взятый из отчет об ошибке) очень прост: a class объявляет метод private и interface объявляет метод public с той же сигнатурой. Он компилируется без ошибок.

Однако, когда я запускаю этот код, я получаю IllegalAccessError

interface I {
    public void m();
}

class A {
    private void m() {
        System.out.println("Inside Class A");
    }

}

abstract class B extends A implements I {
}

class C extends B {
    public void m() {
        System.out.println("Inside Class C");
    }
}

public class Test {
    public static void main(String... args) {
        B b = new C();
        b.m();
    }
}

Пожалуйста, помогите мне понять, почему эта ошибка существует как мой код компилируется отлично.

Exception in thread "main" java.lang.IllegalAccessError:  
tried to access method A.m()V from class Test
    at Test.main(Test.java:25)

Ответы

Ответ 1

Он компилируется, поскольку все кажется прекрасным.

Однако b.m() переводится как поиск подписи m(), в B, очевидно, сначала в A и (предполагается) позже в интерфейсах. В A найден закрытый m() и bang.

Непоследовательное поведение языка и теоретически избегаемое компилятором.


Перефразировано

Во время компиляции найден метод общедоступного интерфейса - отлично. Во время выполнения сигнатура (без модификатора) находится в A, где метод является закрытым, никогда не достигая сигнатуры в интерфейсе, где этот метод является общедоступным.


[FYI] Разборка с помощью javap

invokevirtual method .../.../B.m:()V

Конечно, для объекта C.

Ответ 2

Он компилируется, потому что класс B является абстрактным классом, который объявляет, что он реализует интерфейс я - он предполагает, что реализация будет иметь необходимый метод.

Тип объекта b объявляется как B во время компиляции. Вы можете видеть, что это B, а не C, если вы немного играете, как в приведенных ниже примерах:

Чтобы сделать это простым с примером, если у вас есть новый метод в классе c

class C extends B {
    public void m() {
        System.out.println("C.m");
    }

    public void testFromC() {}
}

тогда попытка вызвать это в основном не будет компилироваться.

public static void main(String[] args) {
    B b = new C();
    b.testFromC(); // doesnt compile
}

Если вы добавите метод в B, это будет нормально.

abstract class B extends A implements I {
    public void testFromB() { }
}

public static void main(String[] args) {
    B b = new C();
    b.testFromB(); // compiles
}

Когда он запускает программу, рассматривая как объект класса B, он обнаруживает реализацию из A, которая является частной. Если вы заставляете b смотреть как тип C путем кастинга, он будет работать.

public static void main(String[] args) {
    B b = new C();
    ((C)b).testFromC();
}

Кроме того, если вы удалите личную реализацию A для m, она также будет работать без кастинга.

class A {
    //private void m() {
    //System.out.println("A.m");
    //}
}

Теперь это работает:

public static void main(String[] args) {
    B b = new C();
    b.m();
}

Итак, как я понимаю сейчас, похоже, что во время выполнения сначала он проверяет метод m на B или его родителя, и если он ничего не находит, он переходит к реализации B, который имеет класс C.

Ответ 3

Это известная проблема и в настоящее время отслеживается здесь:

JDK-8021581 Частные методы класса мешают вызовам методов интерфейса

Этот билет содержит подробный анализ проблемы, обсуждение проблем совместимости и рисков предлагаемых решений и по-прежнему открыт.

Более старые дискуссии по теме можно найти здесь:

JDK-6684387 IllegalAccessError для кода, переданного компилятором
(этот был связан shmosel в его комментарии - спасибо за это)

JDK-6691741 Алгоритм членства JLS слишком силен для разрешения метода JVMS