Пакет java.lang.classfile
Предоставляет библиотеку для парсинга, генерации и трансформации файлов классов.
Thejava.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 isClassModel; we convert bytes into a ClassModel with ClassFile.parse(byte[]): ClassModel cm = ClassFile.of().parse(bytes);
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 theClassModel.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 theCode 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 anAttributeMapper. 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
| Класс | Описание |
|---|---|
| AccessFlags | Моделирует флаги доступа для класса, метода или поля. |
| Annotation | |
| AnnotationElement | |
| 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 | |
| ClassFile.Option | Опция, которая влияет на парсинг или запись файлов class. |
| ClassFile.ShortJumpsOption | Опция, описывающая, нужно ли автоматически переписывать короткие переходы на эквивалентные инструкции при необходимости. |
| ClassFile.StackMapsOption | Опция, описывающая, генерировать ли карты стека. |
|
ClassFileBuilder<E extends ClassFileElement, B extends ClassFileBuilder<E, |
Построитель для CompoundElement, принимающий элементы-члены, которые будут интегрированы в построенную структуру. |
| ClassFileElement | Маркерный интерфейс для структур со специальными возможностями в формате файла
class. |
|
ClassFileTransform<C extends ClassFileTransform<C, |
Преобразование 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 | |
| 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. |
© 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