Java: Как определить, требуется ли локальному классу, определенному в блоке инициализатора, экземпляр-экземпляр для экземпляра?

В настоящее время я реализую метод, который имеет один параметр класса Class, и этот метод возвращает логическое значение, если данный объект класса требует, чтобы экземпляр этого объекта включал класс для его экземпляра.

Этот метод работает в настоящее время следующим образом:

    if (clazz.getEnclosingClass() == null) {
        return false;
    }
    if (clazz.isAnonymousClass() || clazz.isMemberClass()) {
        return !Modifier.isStatic(clazz.getModifiers());
    }
    if (clazz.getEnclosingConstructor() != null) {
        return true;
    }
    final Method enclosingMethod = clazz.getEnclosingMethod();
    if (enclosingMethod != null) {
        return !Modifier.isStatic(enclosingMethod.getModifiers());
    }

Чтобы объяснить, почему он сконструирован как таковой:

  • Сначала он проверяет, является ли он классом верхнего уровня, если это так, алгоритм может безопасно вернуть false
  • Если класс является анонимным или классом-членом, для него требуется закрывающий экземпляр, если он не статичен (класс anynmous является автоматически статическим, если он объявлен в статическом конструкторе/методе/блоке-инициализаторе)
  • Теперь класс можно считать локальным классом (без учета массивов и примитивов), поэтому он определяется как конструктором, так и методом инициализатора. Однако, в отличие от анонимного класса, локальный класс никогда не считается статическим, но для него требуется закрытый экземпляр, если локальный класс определен в нестационарном блоке.
  • Конструктор никогда не статичен, поэтому в этом случае return true
  • Если он определен в методе, верните true, если метод не является статическим

Мне нужен шаг 6, чтобы определить, находится ли локальный класс в блоке статического инициализатора или в блоке инициализатора экземпляра, поэтому я выполнил реализацию для этой функции.

Итак, здесь, где API отражений немного короче. Там нет метода Class.getEnclosingInitializer() или такого, и нет класса, который представляет инициализатор в пакете отражения.

Не является ли инициализатор блоком членом класса? В спецификации java 1.8 интерфейс Member имеет только классы реализации Field, Executable (с подклассами Constructor и Method), а затем есть MemberName, которое выходит за рамки для большинства пользователей отражения.

Я не уверен, что люди, стоящие за спецификацией, забыли об этой ситуации, а локальные классы на самом деле должны быть статическими, если они объявлены в статическом методе/инициализаторе (например, анонимные классы). Но мне кажется, что с этой точки зрения ему не хватает этой последней последовательности.

И есть ли у кого-нибудь идея о том, как определить, в каком типе блока инициализатора объявлен локальный класс?

Я не очень люблю копать поля для синтетического типа, равного ему, охватывающего класс, или прокручивать его конструкторы для чего-то вроде этого (sidenote: объекты Parameter из Constructor.getParameters() всегда возвращают false на isImplicit() и isSynthetic() независимо от того, что я пытаюсь... это просто кажется неправильным). Поэтому, если я могу избежать таких решений, которые были бы замечательными.

Ответ 1

Интересная головоломка, но я боюсь, что она не имеет решения, которое удовлетворяет вашим требованиям. После того, как исходные файлы скомпилированы в байт-код, данные о закрывающей области класса теряются. Помните, что JVM предназначен не только для языка Java, но описанная проблема в основном зависит от языка.


Мне нужен шаг 6, чтобы определить, находится ли локальный класс в Статический блок инициализатора или блок инициализатора экземпляра

Такая информация отсутствует во время выполнения. Файл класса имеет атрибут EnclosingMethod для локальных или анонимных классов (JVMS §4.7.7). Тем не менее, нет возможности различать между включением инициализатора экземпляра и статическим инициализатором. В спецификации явно указано, что

"method_index должен быть равен нулю, если текущий класс немедленно был заключен в исходный код с помощью инициализатора экземпляра, статического инициализатора, инициализатора переменной экземпляра или инициализатора переменной класса".

Рассмотрим эти два случая:

class Outer {
    {
        // Local class inside instance initializer
        class Local {}
    }
}

против.

class Outer {
    static {
        // Local class inside static initializer
        class Local {
            final Outer this$0;

            Local(Outer outer) {
                this$0 = outer;
            }
        }
    }
}

Они скомпилированы почти в один и тот же файл класса. Единственное различие заключается в том, что в поле this$0 есть флаг SYNTHETIC в первом случае, но не во втором. Но проверка этого флага - именно то, чего вы хотите избежать. (Почему?)


локальные классы фактически должны быть статическими, если они объявлены в статическом метод/инициализатор (например, анонимные классы)

Я бы сказал, что ни локальные, ни анонимные классы никогда не статичны. Кроме того, спецификация языка Java требует, чтобы они были нестатическими:

  • Модификатор static относится только к классам-членам, а не к верхним или локальным или анонимным классам (JLS §8.1.1).
  • Анонимный класс всегда является внутренним классом; он никогда не статичен (JLS §15.9.5).

Итак, это явно нарушение спецификации, которое getModifiers() иногда возвращает STATIC для анонимного класса. Существует ошибка JDK-8034044, которая была исправлена ​​в предстоящем JDK 9. И это нарушает шаг 2 вашего алгоритма.


Хорошо, что тогда делать? Это зависит от того, что вы на самом деле имеете в виду

если для данного объекта класса требуется экземпляр класса, охватывающего класс для его создания

Я бы сказал, что вышеприведенное определение подразумевает, что все конструкторы класса имеют дополнительный аргумент класса класса. В этом случае не существует способа создать экземпляр класса без экземпляра охватывающего класса.

Однако, если вы действительно хотите различать инициализатор и статический инициализатор, ваш единственный шанс (как я показал выше) - искать поле с флагом SYNTHETIC.

Ответ 2

Блок инициализатора не является тем, что существует в скомпилированном файле класса. Код в статических инициализационных блоках ставится в статический метод <clinit > , а код в нестатических блоках инициализатора помещается в каждый конструктор класса. Поэтому для локального класса, определенного в блоке нестатического инициализатора, getEnclosingConstructor() должен возвращать ненулевое значение.