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