Java: доступ к защищенным полям из внутреннего класса - PullRequest
17 голосов
/ 13 июля 2020

Недавно я столкнулся с проблемой получения ошибки времени выполнения java.lang.IllegalAccessError при попытке доступа из внутреннего класса к защищенному полю, объявленному во внешнем родительском классе, которое было загружено другим загрузчиком классов. Вкратце:

  1. Класс Parent имеет защищенное поле p.
  2. Класс Outer расширяет Parent.
  3. Класс Inner является внутренним класс, определенный в классе Outer.
  4. Внутри Inner класса есть код: Outer.this.p.
  5. Все классы объявлены в одном пакете.

Обычно он компилируется и работает нормально, пока классы Parent и Outer не будут загружены разными загрузчиками классов. В этом случае мы получаем java.lang.IllegalAccessError при попытке доступа к Outer.this.p из Inner. Я нашел старый отчет об ошибке (который, похоже, был функцией), описывающий это поведение:

https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6258289

Но разрешение звучит мне противоречиво:

Ключ состоит в том, что в неудачном случае внутренний класс не находится в том же пакете (и не является подклассом) ConcreteCommand / AbstractCommand. Это просто нарушение спецификации Java для защищенных классов.

Звучит правильно. Но если мы объявляем классы Parent и Outer в разных пакетах, но загружаем их с помощью одного загрузчика классов (просто создайте образец консольного приложения без каких-либо загрузок jar), мы не получим никаких ошибок. Так что технически это нарушение Java spe c для защищенных классов, но поскольку мы используем внутренний класс, он работает.

Итак, у нас разное поведение для двух случаев «разных пакетов».

  1. Объявлено в разных пакетах, загружено одним загрузчиком классов - ОК.
  2. Объявлено в одном пакете, загружено разными загрузчиками классов - НЕ ОК.

Может ли кто-нибудь дать четкое объяснение того, как внутренний класс получает доступ к родительским полям и почему он работает по-разному в двух случаях?

Ответы [ 2 ]

1 голос
/ 25 июля 2020
  • Тот же загрузчик классов, похоже, работает
  • Правильно ли я понял вопрос?
  • Есть ли у вас какой-либо пример модульного теста для воспроизведения вашей проблемы?

Родительский класс

package p1;

public class Parent {
    
    protected String p = "Value from Parent";
    
    public void test() {
        System.out.println(p);
    }

}

Внешний класс

package p1;

public class Outer extends Parent {

    class Inner {
        public void test() {
            Outer.this.p = "Value set from Inner";
            System.out.println(Outer.this.p);
        }
    }

    public void test() {
        new Inner().test();
    }
}

Основной класс

package p1;

public class Main {

    public static void main(String[] args) {
        Parent p = new Parent();
        p.test();
        p = new Outer();
        p.test();
    }
}

Выход

Value from Parent
Value set from Inner
0 голосов
/ 25 июля 2020

Объявлено в разных пакетах, загруженных одним загрузчиком классов - OK

«защищенный» доступ учитывает родительско-дочерние отношения между классами и позволяет дочерним классам получать доступ к «защищенным» членам родителя, даже если они находятся в разных пакетах. Итак, я думаю, что это так, как ожидалось.

Объявлено в одном пакете, загружено разными загрузчиками классов - НЕ ОК

Это связано с пакетами времени выполнения. Отметьте это . Теперь мы знаем, что родительский пакет находится в другом пакете времени выполнения, чем Внешний и Внутренний, из-за того, что он загружается через два разных загрузчика классов. В то же время мы также должны помнить, что Внешний является «дочерним» по отношению к Родителю, а Внутренний - нет. Inner не имеет отношения Is-a с Parent.

Собираем все вместе: поскольку Parent находится в другом пакете времени выполнения, Inner не может получить доступ к «защищенным» членам Parent, поскольку Inner не имеет дочерний элемент Parent.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...