Spec-Zone.ru › OpenJDK 24

Пакет java.lang.classfile

package java.lang.classfile

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

The java.lang.classfile package contains API models for reading, writing, and modifying Java class files, as specified in Chapter 4 of the Java Virtual Machine Specification. This package, java.lang.classfile.attribute, java.lang.classfile.constantpool, and java.lang.classfile.instruction form the Class-File API.

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

The main class for reading classfiles is ClassModel; we convert bytes into a ClassModel with ClassFile.parse(byte[]):
ClassModel cm = ClassFile.of().parse(bytes);
There are several additional overloads of parse that let you specify various processing options.

A ClassModel is an immutable description of a class file. It provides accessor methods to get at class metadata (e.g., ClassModel.thisClass(), ClassModel.flags()), as well as subordinate classfile entities (ClassModel.fields(), AttributedElement.attributes()). A ClassModel is inflated lazily; most parts of the classfile are not parsed until they are actually needed. Due to the laziness, these models may not be thread safe. Additionally, invocations to accessor methods on models may lead to IllegalArgumentException due to malformed class file format, as parsing happens lazily.

We can enumerate the names of the fields and methods in a class by:

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());

When we enumerate the methods, we get a MethodModel for each method; like a ClassModel, it gives us access to method metadata and the ability to descend into subordinate entities such as the bytecodes of the method body. In this way, a ClassModel is the root of a tree, with children for fields, methods, and attributes, and MethodModel in turn has its own children (attributes, CodeModel, etc.)

Methods like ClassModel.methods() allows us to traverse the class structure explicitly, going straight to the parts we are interested in. This is useful for certain kinds of analysis, but if we wanted to process the whole classfile, we may want something more organized. A ClassModel also provides us with a view of the classfile as a series of class elements, which may include methods, fields, attributes, and more, and which can be distinguished with pattern matching. We could rewrite the above example as:

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 -> { }
    }
}

The models returned as elements from traversing ClassModel can in turn be sources of elements. If we wanted to traverse a classfile and enumerate all the classes for which we access fields and methods, we can pick out the class elements that describe methods, then in turn pick out the method elements that describe the code attribute, and finally pick out the code elements that describe field access and invocation instructions:

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 -> { }
                    }
                }
            }
        }
    }
}

This same query could alternately be processed as a stream pipeline over class elements:

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());

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

The view of classfiles presented by this API is framed in terms of models and elements. Models represent complex structures, such as classes, methods, fields, record elements, or the code body of a method. Models can be explored either via random-access navigation (such as the ClassModel.methods() accessor) or as a linear sequence of elements. (Elements can in turn also be models; a FieldModel is also an element of a class.) For each model type (e.g., MethodModel), there is a corresponding element type (MethodElement). Models and elements are immutable and are inflated lazily so creating a model does not necessarily require processing its entire content.

Константный пул

Much of the interesting content in a classfile lives in the constant pool. ClassModel provides a lazily-inflated, read-only view of the constant pool via ClassModel.constantPool(). Descriptions of classfile content is often exposed in the form of various subtypes of PoolEntry, such as ClassEntry or Utf8Entry.

Constant pool entries are also exposed through models and elements; in the above traversal example, the InvokeInstruction element exposed a method for owner that corresponds to a Constant_Class_info entry in the constant pool.

Атрибуты

Much of the contents of a classfile is stored in attributes; attributes are found on classes, methods, fields, record components, and on the Code attribute. Most attributes are surfaced as elements; for example, SignatureAttribute is a ClassElement, MethodElement, and FieldElement since it can appear in all of those places, and is included when iterating the elements of the corresponding model.

Some attributes are not surfaced as elements; these are attributes that are tightly coupled to -- and logically part of -- other parts of the class file. These include the BootstrapMethods, LineNumberTable, StackMapTable, LocalVariableTable, and LocalVariableTypeTable attributes. These are processed by the library and treated as part of the structure they are coupled to (the entries of the BootstrapMethods attribute are treated as part of the constant pool; line numbers and local variable metadata are modeled as elements of CodeModel.)

The Code attribute, in addition to being modeled as a MethodElement, is also a model in its own right (CodeModel) due to its complex structure.

Each standard attribute has an interface (in java.lang.classfile.attribute) which exposes the contents of the attribute and provides factories to construct the attribute. For example, the Signature attribute is defined by the SignatureAttribute class, and provides accessors for SignatureAttribute.signature() as well as factories taking Utf8Entry or String.

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

Attributes are converted between their classfile form and their corresponding object form via an AttributeMapper. An AttributeMapper provides the AttributeMapper.readAttribute(AttributedElement, ClassReader, int) method for mapping from the classfile format to an attribute instance, and the AttributeMapper.writeAttribute(BufWriter, Attribute) method for mapping back to the classfile format. It also contains metadata including the attribute name, the set of classfile entities where the attribute is applicable, and whether multiple attributes of the same kind are allowed on a single entity.

There are built-in attribute mappers (in Attributes) for each of the attribute types defined in section 4.7 of The Java Virtual Machine Specification, as well as several common nonstandard attributes used by the JDK such as CharacterRangeTable.

Unrecognized attributes are delivered as elements of type UnknownAttribute, which provide access only to the byte[] contents of the attribute.

For nonstandard attributes, user-provided attribute mappers can be specified through the use of the ClassFile.AttributeMapperOption.of(Function)} classfile option. Implementations of custom attributes should extend CustomAttribute.

Параметры

ClassFile.of(ClassFile.Option[]) accepts a list of options. ClassFile.Option is a base interface for some statically enumerated options, as well as factories for more complex options, including:

  • 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.

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

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

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

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

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

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

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

Более сложную проверку файла класса можно выполнить, вызвав ClassFile.verify(java.lang.classfile.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.transformingMethodBodiess(fooToBar);

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

var cc = ClassFile.of();
byte[] newBytes = cc.transform(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.transform(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
Моделирует байтовое значение пары "ключ-значение".
AnnotationValue.OfChar
Моделирует символьное значение пары "ключ-значение".
AnnotationValue.OfClass
Моделирует значение класса пары "ключ-значение".
AnnotationValue.OfConstant
Моделирует константное значение пары "ключ-значение".
AnnotationValue.OfDouble
Моделирует двойное значение пары "ключ-значение".
AnnotationValue.OfEnum
Моделирует значение перечисления пары "ключ-значение".
AnnotationValue.OfFloat
Моделирует значение с плавающей точкой пары "ключ-значение".
AnnotationValue.OfInt
Моделирует целое значение пары "ключ-значение".
AnnotationValue.OfLong
Моделирует длинное целое значение пары "ключ-значение".
AnnotationValue.OfShort
Моделирует короткое целое значение пары "ключ-значение".
AnnotationValue.OfString
Моделирует строковое значение пары "ключ-значение".
Attribute<A extends Attribute<A>>
Моделирует атрибут (JVMS 4.7) в формате файла class.
AttributedElement
A 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, принимающий элементы-члены, которые будут интегрированы в построенную структуру.
END_OF_DOCUMENT_MARKER
ClassFileElement
Маркерный интерфейс для структур со специальными возможностями в формате файла class.
ClassFileTransform<C extends ClassFileTransform<C,E,B>, E extends ClassFileElement, B extends ClassFileBuilder<E,B>>
Преобразование CompoundElement путём обработки его отдельных элементов-членов и отправки результатов в ClassFileBuilder через ClassFileBuilder.transform(java.lang.classfile.CompoundElement<E>, java.lang.classfile.ClassFileTransform<?, E, B>).
ClassFileVersion
Моделирует номера версии minor и major файла 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.
END_OF_DOCUMENT_MARKER
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.

© 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://download.java.net/java/early_access/jdk24/docs/api/java.base/java/lang/classfile/package-summary.html

Spec-Zone.ru

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