Spec-Zone.ru › OpenJDK 27

Пакет java.lang.classfile

package java.lang.classfile

Предоставляет библиотеку для анализа, генерации и преобразования файлов классов.

Пакет java.lang.classfile содержит модели API для чтения, записи и изменения файлов классов Java, как указано в главе 4 Спецификации виртуальной машины Java. Этот пакет, java.lang.classfile.attribute, java.lang.classfile.constantpool и java.lang.classfile.instruction образуют API файлов классов.

Чтение файлов классов

Основным классом для чтения файлов классов является ClassModel; мы преобразуем байты в ClassModel с помощью ClassFile.parse(byte[]):
ClassModel cm = ClassFile.of().parse(bytes);
Существует несколько дополнительных перегруженных вариантов parse, позволяющих задать различные параметры обработки.

ClassModel — это неизменяемое описание файла класса. Он предоставляет методы доступа к метаданным класса (например, ClassModel.thisClass(), ClassModel.flags()), а также к подчинённым сущностям файла класса (ClassModel.fields(), AttributedElement.attributes()). Объект ClassModel создаётся по требованию; большая часть файла класса анализируется только при фактической необходимости. Из-за ленивой обработки эти модели могут быть небезопасны для использования в нескольких потоках. Кроме того, вызовы методов доступа к моделям могут привести к IllegalArgumentException из-за некорректного формата файла class, поскольку анализ выполняется по требованию.

Перечислить имена полей и методов класса можно следующим образом:

ClassModel cm = ClassFile.of().parse(bytes);
for (FieldModel fm : cm.fields())
    System.out.printf("Field %s%n", fm.fieldName().stringValue());
for (MethodModel mm : cm.methods())
    System.out.printf("Method %s%n", mm.methodName().stringValue());

При перечислении методов для каждого из них мы получаем MethodModel; как и ClassModel, он предоставляет доступ к метаданным метода и позволяет перейти к подчинённым сущностям, например к байт-коду тела метода. Таким образом, ClassModel является корнем дерева с дочерними элементами, представляющими поля, методы и атрибуты, а у MethodModel, в свою очередь, есть собственные дочерние элементы (атрибуты, CodeModel и т. д.)

Такие методы, как ClassModel.methods(), позволяют явно обходить структуру класса и сразу переходить к интересующим нас частям. Это полезно для некоторых видов анализа, однако, если требуется обработать весь файл класса, может понадобиться более организованный подход. ClassModel также предоставляет представление файла класса в виде последовательности элементов класса, которая может включать методы, поля, атрибуты и другие элементы, различаемые с помощью сопоставления с образцом. Приведённый выше пример можно переписать так:

ClassModel cm = ClassFile.of().parse(bytes);
for (ClassElement ce : cm) {
    switch (ce) {
        case MethodModel mm -> System.out.printf("Method %s%n", mm.methodName().stringValue());
        case FieldModel fm -> System.out.printf("Field %s%n", fm.fieldName().stringValue());
        default -> { }
    }
}

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

ClassModel cm = ClassFile.of().parse(bytes);
Set<ClassDesc> dependencies = new HashSet<>();

for (ClassElement ce : cm) {
    if (ce instanceof MethodModel mm) {
        for (MethodElement me : mm) {
            if (me instanceof CodeModel xm) {
                for (CodeElement e : xm) {
                    switch (e) {
                        case InvokeInstruction i -> dependencies.add(i.owner().asSymbol());
                        case FieldInstruction i -> dependencies.add(i.owner().asSymbol());
                        default -> { }
                    }
                }
            }
        }
    }
}

Тот же запрос можно обработать как конвейер потоковой обработки элементов класса:

ClassModel cm = ClassFile.of().parse(bytes);
Set<ClassDesc> dependencies =
      cm.elementStream()
        .flatMap(ce -> ce instanceof MethodModel mm ? mm.elementStream() : Stream.empty())
        .flatMap(me -> me instanceof CodeModel com ? com.elementStream() : Stream.empty())
        .<ClassDesc>mapMulti((xe, c) -> {
            switch (xe) {
                case InvokeInstruction i -> c.accept(i.owner().asSymbol());
                case FieldInstruction i -> c.accept(i.owner().asSymbol());
                default -> { }
            }
        })
        .collect(toSet());

Модели и элементы

Представление файлов классов в этом API строится на понятиях моделей и элементов. Модели представляют сложные структуры, такие как классы, методы, поля, элементы записей или тело кода метода. Изучать модели можно как с помощью навигации с произвольным доступом (например, через метод доступа ClassModel.methods()), так и в виде линейной последовательности элементов. (Элементы также могут быть моделями: FieldModel является также элементом класса.) Для каждого типа модели (например, MethodModel) существует соответствующий тип элемента (MethodElement). Модели и элементы неизменяемы и создаются по требованию, поэтому создание модели не обязательно требует обработки всего её содержимого.

Пул констант

Большая часть интересного содержимого файла класса находится в пуле констант. ClassModel предоставляет доступное только для чтения представление пула констант, создаваемое по требованию, через ClassModel.constantPool(). Описания содержимого файла класса часто представлены различными подтипами PoolEntry, например ClassEntry или Utf8Entry.

Записи пула констант также доступны через модели и элементы; в приведённом выше примере обхода элемент InvokeInstruction предоставлял метод для owner, соответствующей записи Constant_Class_info в пуле констант.

Атрибуты

Значительная часть содержимого файла класса хранится в атрибутах; атрибуты встречаются у классов, методов, полей, компонентов записей и у атрибута Code. Большинство атрибутов представлены как элементы; например, SignatureAttribute является ClassElement, MethodElement и FieldElement, поскольку может присутствовать во всех этих местах и включается при переборе элементов соответствующей модели.

Некоторые атрибуты не представлены как элементы; это атрибуты, тесно связанные с другими частями файла класса и логически являющиеся их частью. К ним относятся атрибуты BootstrapMethods, LineNumberTable, StackMapTable, LocalVariableTable и LocalVariableTypeTable. Эти атрибуты обрабатываются библиотекой и считаются частью связанной с ними структуры (записи атрибута BootstrapMethods считаются частью пула констант; номера строк и метаданные локальных переменных моделируются как элементы CodeModel.)

Атрибут Code помимо представления в виде MethodElement является также самостоятельной моделью (CodeModel) из-за своей сложной структуры.

Для каждого стандартного атрибута предусмотрен интерфейс (в java.lang.classfile.attribute), который предоставляет содержимое атрибута и фабричные методы для его создания. Например, атрибут Signature определяется классом SignatureAttribute и предоставляет методы доступа к SignatureAttribute.signature(), а также фабричные методы, принимающие Utf8Entry или String.

Пользовательские атрибуты

Преобразование атрибутов между их представлением в файле класса и соответствующим объектным представлением выполняется с помощью AttributeMapper. AttributeMapper предоставляет метод AttributeMapper.readAttribute(AttributedElement, ClassReader, int) для преобразования из формата файла класса в экземпляр атрибута и метод AttributeMapper.writeAttribute(BufWriter, Attribute) для обратного преобразования в формат файла класса. Он также содержит метаданные, в том числе имя атрибута, набор сущностей файла класса, к которым применим атрибут, и сведения о том, допускается ли наличие нескольких атрибутов одного типа у одной сущности.

Для каждого типа атрибута, определённого в разделе §4.7 Спецификации виртуальной машины Java, существуют встроенные сопоставители атрибутов (в Attributes), а также сопоставители для нескольких распространённых нестандартных атрибутов, используемых JDK, таких как CharacterRangeTable.

Нераспознанные атрибуты передаются как элементы типа UnknownAttribute, предоставляющие доступ только к содержимому атрибута byte[].

Для нестандартных атрибутов пользовательские сопоставители атрибутов можно указать с помощью параметра файла класса ClassFile.AttributeMapperOption.of(Function). Реализации пользовательских атрибутов должны расширять CustomAttribute.

Параметры

ClassFile.of(ClassFile.Option[]) принимает список параметров. ClassFile.Option — базовый интерфейс для некоторых статически перечисленных параметров, а также фабрик для более сложных параметров, включая:

  • ClassFile.AttributeMapperOption.of(Function) -- задаёт формат пользовательских атрибутов
  • ClassFile.AttributesProcessingOption -- обработка нераспознанных или проблемных исходных атрибутов (по умолчанию PASS_ALL_ATTRIBUTES)
  • ClassFile.ClassHierarchyResolverOption.of(ClassHierarchyResolver) -- задаёт пользовательский механизм разрешения иерархии классов, используемый при генерации таблицы карт стека
  • ClassFile.ConstantPoolSharingOption -- совместное использование пула констант при преобразовании (по умолчанию SHARED_POOL)
  • ClassFile.DeadCodeOption -- удаление недостижимого кода (по умолчанию PATCH_DEAD_CODE)
  • ClassFile.DeadLabelsOption -- фильтрация неразрешённых меток (по умолчанию FAIL_ON_DEAD_LABELS)
  • ClassFile.DebugElementsOption -- обработка отладочной информации, например метаданных локальных переменных (по умолчанию PASS_DEBUG)
  • ClassFile.LineNumbersOption -- обработка номеров строк (по умолчанию PASS_LINE_NUMBERS)
  • ClassFile.ShortJumpsOption -- автоматическая замена коротких переходов на длинные при необходимости (по умолчанию FIX_SHORT_JUMPS)
  • ClassFile.StackMapsOption -- генерация карт стека (по умолчанию STACK_MAPS_WHEN_REQUIRED)

ClassFile.AttributeMapperOption и ClassFile.ClassHierarchyResolverOption критически важны для корректного анализа и генерации файлов class. Для анализа пользовательских атрибутов требуется сопоставитель атрибутов. Корректный механизм разрешения необходим для генерации файлов class, байт-код которых ссылается на классы, недоступные системному загрузчику классов, а также в некоторых особых случаях, когда при генерации требуется избежать загрузки системных классов, например в агентах.

Большинство параметров позволяют запросить пропуск определённых частей файла класса при обходе, например отладочной информации или нераспознанных атрибутов. Некоторые параметры позволяют отключить генерацию частей файла класса, например карт стека. Многие из этих параметров позволяют находить компромисс между возможностями и производительностью: обработка отладочной информации и номеров строк требует затрат (как при записи, так и при чтении). Если эти сведения не нужны, их можно отключить с помощью параметров, чтобы повысить производительность.

Запись файлов классов

Генерация файлов классов выполняется с помощью построителей. Для каждого типа сущности, имеющего модель, существует соответствующий тип построителя; классы создаются с помощью ClassBuilder, методы — с помощью MethodBuilder и т. д.

Вместо непосредственного создания построителей они передаются в качестве аргумента пользовательской лямбда-функции. Чтобы сгенерировать знакомую программу «hello world», мы запрашиваем построитель класса и используем его для создания построителей методов конструктора и метода main, а затем используем построители методов для создания атрибута Code и построители кода для генерации инструкций:

byte[] bytes = ClassFile.of().build(CD_Hello,
        clb -> clb.withFlags(ClassFile.ACC_PUBLIC)
                  .withMethod(ConstantDescs.INIT_NAME, ConstantDescs.MTD_void,
                              ClassFile.ACC_PUBLIC,
                              mb -> mb.withCode(
                                      cob -> cob.aload(0)
                                                .invokespecial(ConstantDescs.CD_Object,
                                                               ConstantDescs.INIT_NAME, ConstantDescs.MTD_void)
                                                .return_()))
                  .withMethod("main", MTD_void_StringArray, ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC,
                              mb -> mb.withCode(
                                      cob -> cob.getstatic(CD_System, "out", CD_PrintStream)
                                                .ldc("Hello World")
                                                .invokevirtual(CD_PrintStream, "println", MTD_void_String)
                                                .return_())));

Методы-упрощения ClassBuilder.buildMethodBody позволяют попросить ClassBuilder создать построители кода для непосредственного построения тел методов, минуя пользовательскую лямбда-функцию построителя метода:

byte[] bytes = ClassFile.of().build(CD_Hello,
        clb -> clb.withFlags(ClassFile.ACC_PUBLIC)
                  .withMethodBody(ConstantDescs.INIT_NAME, ConstantDescs.MTD_void,
                                  ClassFile.ACC_PUBLIC,
                                  cob -> cob.aload(0)
                                            .invokespecial(ConstantDescs.CD_Object,
                                                           ConstantDescs.INIT_NAME, ConstantDescs.MTD_void)
                                            .return_())
                  .withMethodBody("main", MTD_void_StringArray, ClassFile.ACC_PUBLIC + ClassFile.ACC_STATIC,
                                  cob -> cob.getstatic(CD_System, "out", CD_PrintStream)
                                            .ldc("Hello World")
                                            .invokevirtual(CD_PrintStream, "println", MTD_void_String)
                                            .return_()));

Построители часто поддерживают несколько способов представления одной и той же сущности на разных уровнях абстракции. Например, инструкцию invokevirtual для вызова println можно было бы сгенерировать с помощью CodeBuilder.invokevirtual, CodeBuilder.invoke или CodeBuilder.with.

Метод-упрощение CodeBuilder.invokevirtual работает так, как если бы он вызывал метод-упрощение CodeBuilder.invoke, который, в свою очередь, работает так, как если бы он вызывал метод CodeBuilder.with. Такое последовательное составление вызовов методов построителя позволяет составлять преобразования (как описано далее).

Символьная информация

Для описания символьной информации о классах и типах API использует абстракции номинальных дескрипторов из java.lang.constant, такие как ClassDesc и MethodTypeDesc, что менее подвержено ошибкам, чем использование обычных строк.

Если для записи пула констант существует номинальное представление, она предоставляет метод, возвращающий соответствующий тип номинального дескриптора; например, метод ClassEntry.asSymbol() возвращает ClassDesc.

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

Проверки согласованности, синтаксические проверки и верификация

API файлов классов выполняет проверки, чтобы убедиться, что аргументы для создания структур файла class представимы в формате файла class. Значение аргумента, которое невозможно представить его типом данных, отклоняется с исключением IllegalArgumentException. Например, значение int не может выходить за пределы своего типа данных; List не может превышать максимально представимый размер своего табличного типа данных или содержать непредставимый элемент. Ограничения, обусловленные базовым типом данных, например упомянутые выше ограничения для int и List, указаны в соответствующих API. Если не указано иное, в любых структурах String не может превышать 65535 байт при представлении в формате модифицированного UTF-8.

Если не указано иное, передача null или массива либо коллекции, содержащих null в качестве элемента, конструктору или методу любого класса или интерфейса API файлов классов приведёт к выбрасыванию исключения NullPointerException.

При построении или преобразовании файлов классов проверки согласованности не выполняются (за исключением проверок на null и представимость аргументов). Все построители и фабричные методы элементов файлов классов принимают предоставленные сведения без неявной проверки, если они представимы в формате файла class. Однако критические несоответствия (например, недопустимая последовательность кода или неразрешённые метки) влияют на работу внутренних инструментов и могут позднее привести к исключениям в процессе построения файла класса. Такие критические исключения выбрасываются как IllegalArgumentException.

Использование номинальных дескрипторов гарантирует, что библиотека API файлов классов применит правильную форму сериализации с учётом контекста. Кроме того, эти номинальные дескрипторы проверяются при создании, поэтому случайно создать их с недопустимым содержимым невозможно. В следующем примере имя класса передаётся методу ClassDesc.of(String) для проверки, а библиотека автоматически преобразует его во внутренний формат имени класса при сериализации в пул констант в виде записи класса.

var validClassEntry = constantPoolBuilder.classEntry(ClassDesc.of("mypackage.MyClass"));

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

var invalidClassEntry = constantPoolBuilder.classEntry(
                            constantPoolBuilder.utf8Entry("mypackage.MyClass"));

Более сложную проверку файла класса можно выполнить вызовом ClassFile.verify(ClassModel).

Преобразование файлов классов

API обработки файлов классов чаще всего используются для объединения чтения и записи при преобразовании: файл класса читается, вносятся локальные изменения, а большая его часть передаётся без изменений. Для каждого типа построителя XxxBuilder имеет метод with(XxxElement), позволяющий непосредственно передавать построителю элементы, которые требуется оставить без изменений.

Если бы мы захотели удалить методы, имена которых начинаются с «debug», мы могли бы получить существующий ClassModel, построить новый файл класса, предоставив ClassBuilder, перебрать элементы исходного ClassModel и передать построителю все элементы, кроме методов, которые требуется удалить:

ClassModel classModel = ClassFile.of().parse(bytes);
byte[] newBytes = ClassFile.of().build(classModel.thisClass().asSymbol(),
        classBuilder -> {
            for (ClassElement ce : classModel) {
                if (!(ce instanceof MethodModel mm
                        && mm.methodName().stringValue().startsWith("debug"))) {
                    classBuilder.with(ce);
                }
            }
        });

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

Преобразование можно рассматривать как операцию «flatMap» над последовательностью элементов: для каждого элемента можно оставить его без изменений, удалить или заменить одним либо несколькими элементами. Поскольку преобразование — очень распространённая операция над файлами классов, для каждого типа модели существует соответствующий тип XxxTransform (описывающий преобразование последовательности XxxElement), а для каждого типа построителя предусмотрены методы transformYyy для преобразования его дочерних моделей. Преобразование — это простой функциональный интерфейс, принимающий построитель и элемент; его реализация применяет «flatMap» к элементам, передавая их построителю. Приведённое выше можно записать так:

ClassTransform ct = (builder, element) -> {
    if (!(element instanceof MethodModel mm && mm.methodName().stringValue().startsWith("debug")))
        builder.with(element);
};
var cc = ClassFile.of();
byte[] newBytes = cc.transformClass(cc.parse(bytes), ct);

Метод-упрощение ClassTransform.dropping позволяет упростить создание того же преобразования и записать приведённый выше пример так:

ClassTransform ct = ClassTransform.dropping(
                            element -> element instanceof MethodModel mm
                                    && mm.methodName().stringValue().startsWith("debug"));

Подъём преобразований

Хотя пример с использованием преобразований лишь немного короче, преимущество такого представления заключается в том, что операции преобразования легче комбинировать. Предположим, мы хотим перенаправить вызовы статических методов класса Foo на соответствующий метод класса Bar. Это можно представить как преобразование CodeElement:
CodeTransform fooToBar = (b, e) -> {
    if (e instanceof InvokeInstruction i
            && i.owner().name().equalsString("Foo")
            && i.opcode() == Opcode.INVOKESTATIC) {
        // remove the old element i by doing nothing to the builder
        // add a new invokestatic instruction to the builder
        b.invokestatic(CD_Bar, i.name().stringValue(), i.typeSymbol(), i.isInterface());
    } else {
        b.with(e);  // leaves the element in place
    }
};

Затем мы можем поднять это преобразование элементов кода до преобразования элементов методов. Оно перехватывает элементы методов, соответствующие атрибуту Code, переходит к его элементам кода и применяет к ним преобразование кода, а остальные элементы методов передаёт без изменений:

MethodTransform mt = MethodTransform.transformingCode(fooToBar);

а затем поднять преобразование элементов методов до преобразования элементов классов:

ClassTransform ct = ClassTransform.transformingMethods(mt);

или сразу поднять преобразование кода до преобразования класса:

ClassTransform ct = ClassTransform.transformingMethodBodies(fooToBar);

а затем преобразовать файл класса:

var cc = ClassFile.of();
byte[] newBytes = cc.transformClass(cc.parse(bytes), ct);

Это намного лаконичнее (и менее подвержено ошибкам), чем эквивалентное преобразование, выраженное через непосредственный обход структуры файла класса:

byte[] newBytes = ClassFile.of().build(classModel.thisClass().asSymbol(),
    classBuilder -> {
      for (ClassElement ce : classModel) {
          if (ce instanceof MethodModel mm) {
              classBuilder.withMethod(mm.methodName().stringValue(), mm.methodTypeSymbol(),
                                      mm.flags().flagsMask(),
                                      methodBuilder -> {
                          for (MethodElement me : mm) {
                              if (me instanceof CodeModel xm) {
                                  methodBuilder.withCode(codeBuilder -> {
                                      for (CodeElement e : xm) {
                                          if (e instanceof InvokeInstruction i && i.owner().asInternalName().equals("Foo")
                                                                       && i.opcode() == Opcode.INVOKESTATIC)
                                                      codeBuilder.invoke(i.opcode(), CD_Bar,
                                                                                    i.name().stringValue(), i.typeSymbol(), i.isInterface());
                                          else codeBuilder.with(e);
                                      }});
                                  }
                                  else
                                  methodBuilder.with(me);
                              }
                          });
                      }
              else
              classBuilder.with(ce);
          }
      });

Композиция преобразований

Преобразования элементов одного типа можно последовательно объединять, передавая результат первого на вход второго. Предположим, мы хотим инструментировать все вызовы методов, выводя имя метода перед его вызовом:
CodeTransform instrumentCalls = (b, e) -> {
    if (e instanceof InvokeInstruction i) {
        b.getstatic(CD_System, "out", CD_PrintStream)
         .ldc(i.name().stringValue())
         .invokevirtual(CD_PrintStream, "println", MTD_void_String);
    }
    b.with(e);
};

Затем можно объединить fooToBar и instrumentCalls с помощью CodeTransform.andThen(CodeTransform):

var cc = ClassFile.of();
byte[] newBytes = cc.transformClass(cc.parse(bytes),
                               ClassTransform.transformingMethods(
                                   MethodTransform.transformingCode(
                                       fooToBar.andThen(instrumentCalls))));
Преобразование instrumentCalls получит все элементы кода, созданные преобразованием forToBar: как элементы кода исходного файла класса, так и замены (при которых статические вызовы Foo заменяются вызовами Bar).

Совместное использование пула констант

Преобразование не ограничивается организацией чтения, преобразования элементов и записи. В большинстве случаев при преобразовании файла класса в него вносятся относительно небольшие изменения. Для оптимизации таких случаев новый файл класса при преобразовании инициализируется копией пула констант исходного файла; это позволяет значительно повысить производительность (методы и атрибуты, которые не преобразуются, можно обрабатывать путём массового копирования их байтов, а не анализа и повторной генерации содержимого). Если совместное использование пула констант нежелательно, его можно отключить с помощью параметра ClassFile.ConstantPoolSharingOption. Такое отключение может быть полезным при удалении множества элементов в результате преобразования, поскольку в пуле констант останется много неиспользуемых записей.

Обработка неизвестных элементов файла класса при преобразовании

Пользовательские преобразования файлов классов могут не учитывать элементы файлов классов, появившиеся в будущих выпусках JDK. Для обеспечения детерминированной стабильности преобразования файлов классов, которым необходимо обрабатывать все элементы файла класса, следует реализовывать так, чтобы они выбрасывали исключения при запуске на более новой версии JDK, если преобразуемый файл класса имеет более новую версию или если появляется новый неизвестный элемент файла класса. Например, следующие фрагменты преобразований выполняют строгую проверку совместимости:
CodeTransform fooToBar = (b, e) -> {
    if (ClassFile.latestMajorVersion() > ClassFile.JAVA_22_VERSION) {
        throw new IllegalArgumentException("Cannot run on JDK > 22");
    }
    switch (e) {
        case ArrayLoadInstruction i -> doSomething(b, i);
        case ArrayStoreInstruction i -> doSomething(b, i);
        default ->  b.with(e);
    }
};
ClassTransform fooToBar = (b, e) -> {
    switch (e) {
        case ClassFileVersion v when v.majorVersion() > ClassFile.JAVA_22_VERSION ->
            throw new IllegalArgumentException("Cannot transform class file version " + v.majorVersion());
        default ->  doSomething(b, e);
    }
};
CodeTransform fooToBar = (b, e) -> {
    switch (e) {
        case ArrayLoadInstruction i -> doSomething(b, i);
        case ArrayStoreInstruction i -> doSomething(b, i);
        case BranchInstruction i -> doSomething(b, i);
        case ConstantInstruction i -> doSomething(b, i);
        case ConvertInstruction i -> doSomething(b, i);
        case DiscontinuedInstruction i -> doSomething(b, i);
        case FieldInstruction i -> doSomething(b, i);
        case InvokeDynamicInstruction i -> doSomething(b, i);
        case InvokeInstruction i -> doSomething(b, i);
        case LoadInstruction i -> doSomething(b, i);
        case StoreInstruction i -> doSomething(b, i);
        case IncrementInstruction i -> doSomething(b, i);
        case LookupSwitchInstruction i -> doSomething(b, i);
        case MonitorInstruction i -> doSomething(b, i);
        case NewMultiArrayInstruction i -> doSomething(b, i);
        case NewObjectInstruction i -> doSomething(b, i);
        case NewPrimitiveArrayInstruction i -> doSomething(b, i);
        case NewReferenceArrayInstruction i -> doSomething(b, i);
        case NopInstruction i -> doSomething(b, i);
        case OperatorInstruction i -> doSomething(b, i);
        case ReturnInstruction i -> doSomething(b, i);
        case StackInstruction i -> doSomething(b, i);
        case TableSwitchInstruction i -> doSomething(b, i);
        case ThrowInstruction i -> doSomething(b, i);
        case TypeCheckInstruction i -> doSomething(b, i);
        case PseudoInstruction i ->  doSomething(b, i);
        default ->
            throw new IllegalArgumentException("An unknown instruction could not be handled by this transformation");
    }
};

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

CodeTransform fooToBar = (b, e) -> {
    switch (e) {
        case ArrayLoadInstruction i -> doSomething(b, i);
        case ArrayStoreInstruction i -> doSomething(b, i);
        default ->  b.with(e);
    }
};

Соглашения API

API в значительной степени основан на модели данных формата файла класса, определяющей каждый вид элемента (включая модели и атрибуты) и его свойства. Для каждого вида элемента существует соответствующий интерфейс, описывающий этот элемент, и фабричные методы для его создания. Для некоторых видов элементов также предусмотрены методы-упрощения в соответствующем построителе (например, CodeBuilder.invokevirtual(ClassDesc, String, MethodTypeDesc)).

Большая часть символьной информации в элементах представлена записями пула констант (например, владелец поля представлен как ClassEntry.) Фабричные методы и построители также принимают номинальные дескрипторы из java.lang.constant (например, ClassDesc.)

Стандартные типы данных

В главе 4 Спецификации виртуальной машины Java определено несколько стандартных типов данных в формате файла class. В модели API они единообразно представлены как int. Передача API значений этих типов данных, выходящих за допустимые пределы, приводит к исключению IllegalArgumentException.
u1
Однобайтовое беззнаковое целое число в диапазоне [0, 255].
См. DataInput.readUnsignedByte().
u2
Двухбайтовое беззнаковое целое число в диапазоне [0, 65535].
Эквивалентно типу Java char. Часто используется для полей флагов, индексов и размеров структур списков.
См. DataInput.readUnsignedShort().
u4
Четырёхбайтовое беззнаковое целое число в диапазоне [0, 4294967295].
См. DataInput.readInt().

Модель данных

Каждый вид элемента определяется его именем, необязательным указанием кратности (ноль или больше, ноль или один, ровно один) и списком компонентов. Элементами класса являются поля, методы и атрибуты, которые могут присутствовать у классов:
ClassElement =
    FieldModel*(Utf8Entry name, Utf8Entry descriptor)
    | MethodModel*(Utf8Entry name, Utf8Entry descriptor)
    | ModuleAttribute?(int flags, ModuleEntry moduleName, Utf8Entry moduleVersion,
                       List<ModuleRequireInfo> requires, List<ModuleOpenInfo> opens,
                       List<ModuleExportInfo> exports, List<ModuleProvidesInfo> provides,
                       List<ClassEntry> uses)
    | ModulePackagesAttribute?(List<PackageEntry> packages)
    | ModuleTargetAttribute?(Utf8Entry targetPlatform)
    | ModuleHashesAttribute?(Utf8Entry algorithm, List<HashInfo> hashes)
    | ModuleResolutionAttribute?(int resolutionFlags)
    | SourceFileAttribute?(Utf8Entry sourceFile)
    | SourceDebugExtensionsAttribute?(byte[] contents)
    | CompilationIDAttribute?(Utf8Entry compilationId)
    | SourceIDAttribute?(Utf8Entry sourceId)
    | NestHostAttribute?(ClassEntry nestHost)
    | NestMembersAttribute?(List<ClassEntry> nestMembers)
    | RecordAttribute?(List<RecordComponent> components)
    | EnclosingMethodAttribute?(ClassEntry className, NameAndTypeEntry method)
    | InnerClassesAttribute?(List<InnerClassInfo> classes)
    | PermittedSubclassesAttribute?(List<ClassEntry> permittedSubclasses)
    | DeclarationElement*
где DeclarationElement — элементы, общие для всех объявлений (классов, методов, полей), поэтому они вынесены отдельно:
DeclarationElement =
    SignatureAttribute?(Utf8Entry signature)
    | SyntheticAttribute?()
    | DeprecatedAttribute?()
    | RuntimeInvisibleAnnotationsAttribute?(List<Annotation> annotations)
    | RuntimeVisibleAnnotationsAttribute?(List<Annotation> annotations)
    | CustomAttribute*
    | UnknownAttribute*
Поля и методы являются моделями со своими элементами. Элементы полей и методов довольно просты; большая часть сложности методов сосредоточена в CodeModel (которая моделирует атрибут Code вместе с атрибутами, связанными с кодом: таблицей карт стека, таблицей локальных переменных, таблицей номеров строк и т. д.)
FieldElement =
    DeclarationElement
    | ConstantValueAttribute?(ConstantValueEntry constant)

MethodElement =
    DeclarationElement
    | CodeModel?()
    | AnnotationDefaultAttribute?(ElementValue defaultValue)
    | MethodParametersAttribute?(List<MethodParameterInfo> parameters)
    | ExceptionsAttribute?(List<ClassEntry> exceptions)
CodeModel уникальна тем, что её элементы упорядочены
. Элементы Code включают обычные байткоды, а также ряд псевдоинструкций, представляющих цели переходов, метаданные номеров строк, метаданные локальных переменных и блоки catch.
CodeElement = Instruction | PseudoInstruction

Instruction =
    LoadInstruction(TypeKind type, int slot)
    | StoreInstruction(TypeKind type, int slot)
    | IncrementInstruction(int slot, int constant)
    | BranchInstruction(Opcode opcode, Label target)
    | LookupSwitchInstruction(Label defaultTarget, List<SwitchCase> cases)
    | TableSwitchInstruction(Label defaultTarget, int low, int high,
                             List<SwitchCase> cases)
    | ReturnInstruction(TypeKind kind)
    | ThrowInstruction()
    | FieldInstruction(Opcode opcode, FieldRefEntry field)
    | InvokeInstruction(Opcode opcode, MemberRefEntry method, boolean isInterface)
    | InvokeDynamicInstruction(InvokeDynamicEntry invokedynamic)
    | NewObjectInstruction(ClassEntry className)
    | NewReferenceArrayInstruction(ClassEntry componentType)
    | NewPrimitiveArrayInstruction(TypeKind typeKind)
    | NewMultiArrayInstruction(ClassEntry componentType, int dims)
    | ArrayLoadInstruction(Opcode opcode)
    | ArrayStoreInstruction(Opcode opcode)
    | TypeCheckInstruction(Opcode opcode, ClassEntry className)
    | ConvertInstruction(TypeKind from, TypeKind to)
    | OperatorInstruction(Opcode opcode)
    | ConstantInstruction(ConstantDesc constant)
    | StackInstruction(Opcode opcode)
    | MonitorInstruction(Opcode opcode)
    | NopInstruction()

PseudoInstruction =
    | LabelTarget(Label label)
    | LineNumber(int line)
    | ExceptionCatch(Label tryStart, Label tryEnd, Label handler, ClassEntry exception)
    | LocalVariable(int slot, Utf8Entry name, Utf8Entry type, Label startScope, Label endScope)
    | LocalVariableType(int slot, Utf8Entry name, Utf8Entry type, Label startScope, Label endScope)
    | CharacterRange(int rangeStart, int rangeEnd, int flags, Label startScope, Label endScope)
Начиная с:
24
Пакет Описание
java.lang
Содержит классы, фундаментальные для проектирования языка программирования Java.
java.lang.classfile.attribute
Содержит интерфейсы, описывающие атрибуты файлов class для библиотеки java.lang.classfile.
java.lang.classfile.constantpool
Содержит интерфейсы, описывающие записи пула констант для библиотеки java.lang.classfile.
java.lang.classfile.instruction
Содержит интерфейсы, описывающие инструкции кода для библиотеки java.lang.classfile.
Класс Описание
AccessFlags
Представляет флаги доступа класса, метода или поля.
Annotation
Представляет структуру annotation (JVMS §4.7.16) или часть структуры type_annotation (JVMS §4.7.20).
AnnotationElement
Представляет пару «элемент-значение» в таблице element_value_pairs структуры annotation, определённой в JVMS §4.7.16, или структуры type_annotation, определённой в JVMS §4.7.20.
AnnotationValue
Представляет структуру element_value или значение пары «элемент-значение» аннотации, как определено в JVMS §4.7.16.1.
AnnotationValue.OfAnnotation
Представляет значение-аннотацию пары «элемент-значение».
AnnotationValue.OfArray
Представляет значение-массив пары «элемент-значение».
AnnotationValue.OfBoolean
Представляет логическое значение пары «элемент-значение».
AnnotationValue.OfByte
Представляет значение типа byte пары «элемент-значение».
AnnotationValue.OfChar
Представляет значение типа char пары «элемент-значение».
AnnotationValue.OfClass
Представляет значение-класс пары «элемент-значение».
AnnotationValue.OfConstant
Представляет значение-константу пары «элемент-значение».
AnnotationValue.OfDouble
Представляет значение типа double пары «элемент-значение».
AnnotationValue.OfEnum
Представляет значение-перечисление пары «элемент-значение».
AnnotationValue.OfFloat
Представляет значение типа float пары «элемент-значение».
AnnotationValue.OfInt
Представляет значение типа int пары «элемент-значение».
AnnotationValue.OfLong
Представляет значение типа long пары «элемент-значение».
AnnotationValue.OfShort
Представляет значение типа short пары «элемент-значение».
AnnotationValue.OfString
Представляет строковое значение пары «элемент-значение».
Attribute<A extends Attribute<A>>
Представляет атрибут (JVMS §4.7) в формате файлов class.
AttributedElement
ClassFileElement, описывающий структуру файла class, имеющую атрибуты, например файл class, поле, метод, атрибут Code или компонент записи.
AttributeMapper<A extends Attribute<A>>
Двунаправленный преобразователь между представлением атрибута в файле class и его моделью API.
AttributeMapper.AttributeStability
Указывает зависимость представления атрибута в файле class от данных.
Attributes
Преобразователи атрибутов для предопределённых (JVMS §4.7) и нестандартных атрибутов, специфичных для JDK.
BootstrapMethodEntry
Представляет запись в таблице методов начальной загрузки.
BufWriter
Расширенные средства записи файлов class для AttributeMapper.
ClassBuilder
Построитель файла class.
ClassElement
Маркерный интерфейс для элемента-члена ClassModel.
ClassFile
Предоставляет возможность анализировать, преобразовывать и создавать файлы class.
ClassFile.AttributeMapperOption
Параметр, описывающий пользовательские атрибуты для анализа файлов class.
ClassFile.AttributesProcessingOption
Параметр, определяющий, следует ли сохранять или отбрасывать атрибуты, корректность которых невозможно проверить после преобразования.
ClassFile.ClassHierarchyResolverOption
Параметр, указывающий разрешатель иерархии классов, который следует использовать при создании карт стека или проверке классов.
ClassFile.ConstantPoolSharingOption
Параметр, определяющий, следует ли расширять исходный пул констант при преобразовании файла class.
ClassFile.DeadCodeOption
Параметр, определяющий, следует ли удалять недостижимый код для создания карт стека.
ClassFile.DeadLabelsOption
Параметр, определяющий, следует ли фильтровать непривязанные метки и, если возможно, удалять содержащие их структуры.
ClassFile.DebugElementsOption
Параметр, определяющий, следует ли обрабатывать или отбрасывать отладочные PseudoInstruction при обходе CodeModel или CodeBuilder.
ClassFile.LineNumbersOption
Параметр, определяющий, следует ли обрабатывать или отбрасывать LineNumber при обходе CodeModel или CodeBuilder.
ClassFile.Option
Параметр, влияющий на анализ или запись файлов class.
ClassFile.ShortJumpsOption
Параметр, определяющий, следует ли автоматически заменять короткие переходы эквивалентными инструкциями при необходимости.
ClassFile.StackMapsOption
Параметр, определяющий, следует ли создавать карты стека.
ClassFileBuilder<E extends ClassFileElement, B extends ClassFileBuilder<E,B>>
Построитель CompoundElement, принимающий элементы-члены, которые следует включить в создаваемую структуру.
ClassFileElement
Маркерный интерфейс для структур со специальными возможностями в формате файлов class.
ClassFileTransform<C extends ClassFileTransform<C,E,B>, E extends ClassFileElement, B extends ClassFileBuilder<E,B>>
Преобразование CompoundElement путём обработки отдельных элементов-членов и передачи результатов ClassFileBuilder посредством ClassFileBuilder.transform(CompoundElement, ClassFileTransform).
ClassFileVersion
Представляет младший и старший номера версии файла class (JVMS §4.1).
ClassHierarchyResolver
Предоставляет сведения об иерархии классов для создания карт стека и проверки.
ClassHierarchyResolver.ClassHierarchyInfo
Сведения о разрешённом классе.
ClassModel
Представляет файл class.
ClassReader
Расширенные средства чтения файлов class для AttributeMapper.
ClassSignature
Представляет обобщённую сигнатуру класса или интерфейса, определённую в JVMS §4.7.9.1.
ClassTransform
Преобразование потоков ClassElement.
CodeBuilder
Построитель атрибутов Code (тел методов).
CodeBuilder.BlockCodeBuilder
Построитель блоков кода.
CodeBuilder.CatchBuilder
Построитель для добавления блоков catch.
CodeElement
Маркерный интерфейс для элемента-члена CodeModel.
CodeModel
Представляет тело метода (атрибут Code).
CodeTransform
Преобразование потоков CodeElement.
CompoundElement<E extends ClassFileElement>
Структура файла class, которую можно рассматривать как совокупность входящих в неё структур-членов.
CustomAttribute<T extends CustomAttribute<T>>
Представляет пользовательский атрибут в файле class.
FieldBuilder
Построитель полей.
FieldElement
Маркерный интерфейс для элемента-члена FieldModel.
FieldModel
Представляет поле.
FieldTransform
Преобразование потоков FieldElement.
Instruction
Представляет исполняемую инструкцию в массиве code атрибута Code метода.
Interfaces
Представляет интерфейсы класса (JVMS §4.1).
Label
Метка позиции среди инструкций тела метода.
MethodBuilder
Построитель методов.
MethodElement
Маркерный интерфейс для элемента-члена MethodModel.
MethodModel
Представляет метод.
MethodSignature
Представляет обобщённую сигнатуру метода или конструктора, определённую в JVMS §4.7.9.1.
MethodTransform
Преобразование потоков MethodElement.
Opcode
Описывает коды операций набора инструкций JVM, приведённые в JVMS §6.5.
Opcode.Kind
Виды кодов операций.
PseudoInstruction
Представляет метаданные о CodeModel, полученные непосредственно из атрибута Code или его атрибутов.
Signature
Представляет сигнатуры обобщённых типов Java, определённые в JVMS §4.7.9.1.
Signature.ArrayTypeSig
Представляет сигнатуру типа массива.
Signature.BaseTypeSig
Представляет сигнатуру примитивного типа (JLS §4.2) или void.
Signature.ClassTypeSig
Представляет сигнатуру типа класса или интерфейса, который может иметь параметры типа.
Signature.RefTypeSig
Представляет сигнатуру ссылочного типа, которым может быть класс, интерфейс, переменная типа или тип массива.
Signature.ThrowableSig
Маркерный интерфейс для сигнатуры типа, который можно выбросить.
Signature.TypeArg
Представляет аргумент типа — аргумент параметра типа.
Signature.TypeArg.Bounded
Представляет аргумент типа с явно заданной границей типа.
Signature.TypeArg.Bounded.WildcardIndicator
Представляет обозначение типа-аргумента с подстановочным знаком.
Signature.TypeArg.Unbounded
Представляет аргумент типа с неограниченным подстановочным знаком * или ? в программах Java.
Signature.TypeParam
Представляет сигнатуру параметра типа обобщённого класса, интерфейса, метода или конструктора, который вводит переменную типа.
Signature.TypeVarSig
Представляет сигнатуру переменной типа.
Superclass
Представляет суперкласс класса (JVMS §4.1).
TypeAnnotation
Представляет структуру type_annotation (JVMS §4.7.20).
TypeAnnotation.CatchTarget
Указывает, что аннотация присутствует на типе с индексом i в объявлении параметра исключения.
TypeAnnotation.EmptyTarget
Указывает, что аннотация присутствует на типе в объявлении поля, возвращаемом типе метода, типе вновь созданного объекта или типе-получателе метода или конструктора.
TypeAnnotation.FormalParameterTarget
Указывает, что аннотация присутствует на типе в объявлении формального параметра метода, конструктора или лямбда-выражения.
TypeAnnotation.LocalVarTarget
Указывает, что аннотация присутствует на типе в объявлении локальной переменной, включая переменную, объявленную как ресурс в операторе try-with-resources.
TypeAnnotation.LocalVarTargetInfo
Указывает диапазон смещений в массиве кода, в пределах которого локальная переменная имеет значение, а также индекс этой переменной в массиве локальных переменных текущего кадра.
TypeAnnotation.OffsetTarget
Указывает, что аннотация присутствует на типе в выражении instanceof или выражении создания объекта new либо на типе перед :: в выражении ссылки на метод.
TypeAnnotation.SupertypeTarget
Указывает, что аннотация присутствует на типе в предложении extends или implements объявления класса или интерфейса.
TypeAnnotation.TargetInfo
Указывает, какой тип в объявлении или выражении аннотируется.
TypeAnnotation.TargetType
Вид цели, на которой расположена аннотация, как определено в JVMS §4.7.20.1.
TypeAnnotation.ThrowsTarget
Указывает, что аннотация присутствует на типе с индексом i в предложении throws объявления метода или конструктора.
TypeAnnotation.TypeArgumentTarget
Указывает, что аннотация присутствует либо на типе с индексом i в выражении приведения типа, либо на аргументе типа с индексом i в явном списке аргументов типа для одного из следующих элементов: выражения создания объекта new, оператора явного вызова конструктора, выражения вызова метода или выражения ссылки на метод.
TypeAnnotation.TypeParameterBoundTarget
Указывает, что аннотация присутствует на границе с индексом i параметра типа с индексом j в объявлении обобщённого класса, интерфейса, метода или конструктора.
TypeAnnotation.TypeParameterTarget
Указывает, что аннотация присутствует в объявлении параметра типа с индексом i обобщённого класса, обобщённого интерфейса, обобщённого метода или обобщённого конструктора.
TypeAnnotation.TypePathComponent
JVMS: структура Type_path определяет, какая часть типа аннотирована, как указано в JVMS §4.7.20.2
TypeAnnotation.TypePathComponent.Kind
Вид пути к типу, как определено в JVMS §4.7.20.2
TypeKind
Описывает типы данных, с которыми работает виртуальная машина Java.

Сообщить об ошибке или предложить улучшение
Для получения дополнительных справочных материалов по API и документации для разработчиков см. Документацию Java SE, которая содержит более подробные описания для разработчиков, концептуальные обзоры, определения терминов, обходные решения и примеры работающего кода. Другие версии.
Java является товарным знаком или зарегистрированным товарным знаком Oracle и/или её аффилированных лиц в США и других странах.
Авторское право © 1993, 2026, Oracle и/или её аффилированные лица, 500 Oracle Parkway, Redwood Shores, CA 94065 USA.
Все права защищены. Использование регулируется условиями лицензии и политикой распространения документации.

© 1993, 2025, Oracle and/or its affiliates. All rights reserved.
Documentation extracted from Debian's OpenJDK Development Kit package.
Licensed under the GNU General Public License, version 2, with the Classpath Exception.
Various third party code in OpenJDK is licensed under different licenses (see Debian package).
Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API