Spec-Zone.ru › OpenJDK 25

Пакет 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. Такое объединение вызовов методов построителя позволяет объединять преобразования (описанные далее).

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

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

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

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

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

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

При создании или преобразовании файлов классов проверки согласованности не выполняются (за исключением проверки аргументов на null). Все построители и фабричные методы элементов файла класса принимают предоставленную информацию без неявной проверки. Однако критические несоответствия (например, недопустимая последовательность кода или неразрешённые метки) влияют на внутренние инструменты и могут позднее вызвать исключения в процессе создания файла класса. Такие критические исключения выбрасываются как 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.)

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

Каждый вид элемента определяется его именем, необязательным индикатором кратности (ноль или более, ноль или один, ровно один) и списком компонентов. Элементами класса являются поля, методы и атрибуты, которые могут присутствовать у классов:
ClassElement =
    FieldModel*(UtfEntry name, Utf8Entry descriptor)
    | MethodModel*(UtfEntry name, Utf8Entry descriptor)
    | ModuleAttribute?(int flags, ModuleEntry moduleName, UtfEntry 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 включают обычный байт-код, а также ряд псевдоинструкций, представляющих цели ветвлений, метаданные номеров строк, метаданные локальных переменных и блоки перехвата исключений.
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, UtfEntry 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, 2025, 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.
https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/classfile/package-summary.html

Spec-Zone.ru

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