パッケージjava.lang.classfile
クラス・ファイルの解析、生成および変換ライブラリを提供します。
java.lang.classfile
パッケージには、「Java Virtual Machine仕様」の第4章で指定されたJavaクラス・ファイルの読取り、書込みおよび変更のためのAPIモデルが含まれています。 このパッケージjava.lang.classfile.attribute
、java.lang.classfile.constantpool
およびjava.lang.classfile.instruction
は、クラス・ファイルAPIを形成します。
クラス・ファイルの読取り
クラス・ファイルを読み取るためのメイン・クラスはClassModel
です。ClassFile.parse(byte[])
でバイトをClassModel
に変換します:
ClassModel cm = ClassFile.of().parse(bytes);
parse
には、様々な処理オプションを指定できるオーバーロードがいくつか追加されています。
ClassModel
は、クラス・ファイルの不変の説明です。 クラス・メタデータ(e.g., ClassModel.thisClass()
, ClassModel.flags()
)および下位クラス・ファイル・エンティティ(ClassModel.fields()
, AttributedElement.attributes()
)を取得するためのアクセッサ・メソッドを提供します。 ClassModel
は遅延的に膨張します。クラス・ファイルのほとんどの部分は、実際に必要になるまで解析されません。 遅延のため、これらのモデルはスレッド・セーフではない可能性があります。 また、モデルのアクセッサ・メソッドを呼び出すと、解析が遅延して発生するため、 class
ファイル形式が正しくないため、IllegalArgumentException
が発生する可能性があります。
次の方法で、クラスのフィールドおよびメソッドの名前を列挙できます。
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());
メソッドを列挙すると、ClassModel
のように、各メソッドのMethodModel
が取得され、メソッド・メタデータへのアクセスと、メソッド本文のバイトコードなどの下位エンティティの子孫になる機能が提供されます。 このようにして、ClassModel
はツリーのルートであり、フィールド、メソッドおよび属性の子を持ち、MethodModel
は独自の子(attributes, CodeModel
, etc.)を持ちます。
ClassModel.methods()
のようなメソッドを使用すると、クラス構造を明示的に横断して、関心のある部分に直接移動できます。 これは特定の種類の分析に役立ちますが、クラス・ファイル全体を処理する場合は、より整理されたものが必要な場合があります。 また、ClassModel
は、クラス・ファイルのビューを一連のクラスelementsとして提供します。このクラスには、メソッド、フィールド、属性などが含まれ、パターン一致と区別できます。 前述の例は次のように書き換えることができます。
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によって提示されるクラス・ファイルのビューは、「モデル」およびelementsの観点からフレーム化されます。 モデルは、クラス、メソッド、フィールド、レコード要素、メソッドのコード本文などの複雑な構造を表します。 モデルは、ランダム・アクセス・ナビゲーション(ClassModel.methods()
アクセッサなど)またはelementsの線形シーケンスとして探索できます。 (また、要素はモデルでもかまいません。FieldModel
はクラスの要素でもあります。) モデル・タイプ(e.g., MethodModel
)ごとに、対応する要素タイプ(MethodElement
)があります。 モデルと要素は不変であり、遅延して膨張するため、モデルの作成では必ずしもコンテンツ全体を処理する必要はありません。
定数プール
クラス・ファイルの興味深いコンテンツの多くは、「定数プール」にあります。ClassModel
は、ClassModel.constantPool()
を介して定数プールのレイジー・インフレーションの読取り専用ビューを提供します。 クラス・ファイルの内容の説明は、ClassEntry
やUtf8Entry
など、PoolEntry
の様々なサブタイプの形式で公開されることがよくあります。
定数プール・エントリもモデルおよび要素を介して公開されます。前述のトラバーサル例では、InvokeInstruction
要素が、定数プールの Constant_Class_info
エントリに対応するowner
のメソッドを公開しました。
属性
クラス・ファイルの内容の多くは属性に格納され、属性はクラス、メソッド、フィールド、レコード・コンポーネントおよびCode
属性にあります。 ほとんどの属性は要素として表されます。たとえば、SignatureAttribute
はClassElement
、MethodElement
およびFieldElement
です。これは、これらのすべての場所に存在し、対応するモデルの要素の反復時に含まれるためです。
一部の属性は要素として表示されません。これらは密接に結合された属性です。 -- 論理的な部分 -- クラス・ファイルの他の部分。 これには、BootstrapMethods
, LineNumberTable
, StackMapTable
, LocalVariableTable
属性および LocalVariableTypeTable
属性が含まれます。 これらはライブラリによって処理され、(BootstrapMethods
属性のエントリは、定数プールの一部として扱われます。行番号およびローカル変数メタデータは、CodeModel
の要素としてモデル化されます。)と結合された構造の一部として処理されます
Code
属性は、MethodElement
としてモデル化されることに加えて、その複雑な構造のために、独自の右(CodeModel
)のモデルでもあります。
各標準属性にはインタフェース(in java.lang.classfile.attribute
)があり、このインタフェースは属性の内容を公開し、属性を構成するためのファクトリを提供します。 たとえば、Signature
属性はSignatureAttribute
クラスによって定義され、SignatureAttribute.signature()
およびUtf8Entry
またはString
を使用するファクトリのアクセッサを提供します。
カスタム属性
属性は、AttributeMapper
を介して、クラス・ファイル形式と対応するオブジェクト形式間で変換されます。 AttributeMapper
は、クラス・ファイル形式から属性インスタンスにマッピングするためのAttributeMapper.readAttribute(AttributedElement, ClassReader, int)
メソッド、およびクラス・ファイル形式にマッピングするためのAttributeMapper.writeAttribute(BufWriter, Attribute)
メソッドを提供します。 また、属性名、属性が適用可能なクラス・ファイル・エンティティのセット、同じ種類の複数の属性を単一のエンティティで許可するかどうかなどのメタデータも含まれます。
「Java Virtual Machine仕様」のセクション4.7で定義される各属性タイプには、組込み属性マッパー(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
-- 必要に応じて、ショート・ジャンプをlongに自動的にリライトします。(デフォルトはFIX_SHORT_JUMPS
です)ClassFile.StackMapsOption
-- スタック・マップを生成 (デフォルトはSTACK_MAPS_WHEN_REQUIRED
です)
ClassFile.AttributeMapperOption
およびClassFile.ClassHierarchyResolverOption
は、class
ファイルの解析および生成の正確性にとって重要です。 カスタム属性の解析には属性マッパーが必要です。 正しいリゾルバは、システム・クラス・ローダーがバイトコードで使用できないクラスを参照するclass
ファイルを生成するために必要です。また、生成時にエージェントなどのシステム・クラスのロードを回避する必要がある場合には、コーナー・ケースで必要です。
ほとんどのオプションでは、デバッグ情報や認識されない属性など、トラバース中にクラス・ファイルの特定の部分をスキップするようにリクエストできます。 一部のオプションでは、スタック・マップなど、クラス・ファイルの一部の生成を抑制できます。 これらのオプションの多くは、パフォーマンスのトレードオフにアクセスすることであり、デバッグ情報および行番号の処理にはコスト(書くことと読むことの両方。)があります。 この情報が必要ない場合は、オプションで抑制してパフォーマンスを向上させることができます。
クラス・ファイルの書込み
ClassFileの生成は、buildersによって行われます。 モデルを持つエンティティ・タイプごとに、対応するビルダー・タイプもあります。クラスは、ClassBuilder
、MethodBuilder
などのメソッドを使用して構築されます。
ビルダーは、直接作成するのではなく、ユーザー指定のラムダへの引数として提供されます。 使い慣れた"こんにちは世界"プログラムを生成するには、クラス・ビルダーを要求し、そのクラス・ビルダーを使用してコンストラクタおよび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_()));
ビルダーは、異なるレベルの抽象化で同じエンティティを表現する複数の方法をサポートすることがよくあります。 たとえば、println
を起動するinvokevirtual
命令は、CodeBuilder.invokevirtual
、CodeBuilder.invoke
またはCodeBuilder.with
で生成されている可能性があります。
コンビニエンス・メソッドCodeBuilder.invokevirtual
は、コンビニエンス・メソッドCodeBuilder.invoke
をコールするかのように動作し、次にメソッドCodeBuilder.with
をコールするかのように動作します。 このビルダーでのメソッド・コールの構成により、変換(後で説明します)を作成できます。
特に記載がないかぎり、null
引数をクラス・ファイルAPIクラスまたはインタフェースのコンストラクタまたはメソッドに渡すと、NullPointerException
がスローされます。 さらに、null
要素を含む配列またはコレクションを含むメソッドを呼び出すと、特に指定されていないかぎり、NullPointerException
が発生します。
シンボリック情報
このAPIでは、クラスおよびタイプのシンボリック情報を記述するために、ClassDesc
やMethodTypeDesc
などのjava.lang.constant
の名目記述子抽象化が使用されます。これは、RAW文字列を使用するよりもエラーが発生しやすいものです。
定数プール・エントリに名目表現がある場合は、対応する名目記述子タイプを返すメソッドを提供します。たとえば、メソッドClassEntry.asSymbol()
はClassDesc
を返します。
適切なビルダーは、シンボル情報を持つ要素を構築するための2つのメソッドを提供します。1つは名目記述子を受け入れ、もう1つは定数プール・エントリを受け入れます。
一貫性チェック、構文チェック、検証
クラス・ファイル(null引数チェックを除く)の構築中または変換中に整合性チェックは実行されません。 すべてのビルダーおよびクラス・ファイル要素のファクトリ・メソッドは、暗黙的な検証なしで提供された情報を受け入れます。 ただし、致命的な不整合(例:無効なコード・シーケンスまたは未解決のラベル)は内部ツールに影響し、後でクラス・ファイルの構築プロセスで例外が発生する可能性があります。 これらの致命的な例外は、IllegalArgumentException
としてスローされます。
名目記述子を使用すると、実際のコンテキストに基づいて、ClassFile APIライブラリによって正しいシリアル形式が確実に適用されます。 また、これらの名目記述子は構築中に検証されるため、誤って無効な内容で作成することはできません。 次の例では、検証のためにクラス名をClassDesc.of(java.lang.String)
メソッドに渡し、クラス・エントリとして定数プールで直列化すると、ライブラリはクラス名の正しい内部形式への自動変換を実行します。
var validClassEntry = constantPoolBuilder.classEntry(ClassDesc.of("mypackage.MyClass"));
一方、定数プール・エントリを直接受け入れるビルダー・メソッドおよびファクトリを使用できます。 定数プール・エントリは、RAW値からも直接作成でき、追加の変換や検証は不要です。 次の例では、意図的に間違ったクラス名フォームを使用し、検証または変換を行わずに適用されます。
var invalidClassEntry = constantPoolBuilder.classEntry(
constantPoolBuilder.utf8Entry("mypackage.MyClass"));
クラス・ファイルのより複雑な検証は、ClassFile.verify(java.lang.classfile.ClassModel)
を呼び出すことによって実現できます。
クラス・ファイルの変換
ClassFile処理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"操作とみなすことができます。すべての要素について、変更されていない要素に渡すか、削除するか、または1つ以上の要素で置換できます。 変換はクラス・ファイルに対して非常に一般的な操作であるため、各モデル型には対応する 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
}
};
次に、コード要素に対するこの変換をメソッド要素の変換にliftできます。 これは、Code
属性に対応するメソッド要素をインターセプトし、そのコード要素にダイブしてコード変換を適用し、変更せずに他のメソッド要素を渡します。
MethodTransform mt = MethodTransform.transformingCode(fooToBar);
さらに、メソッド要素の変換をクラス要素の1つにリフトします。
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);
}
});
変換の作成
同じタイプの要素の変換を順番に構成でき、最初の要素の出力は2番目の要素の入力に送られます。 すべてのメソッド・コールをインストゥルメントし、メソッドをコールする前にメソッドの名前を出力するとします。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は、主にクラス・ファイル形式の「データ・モデル」から導出され、各要素の種類(モデルと属性を含む)とそのプロパティを定義します。 要素の種類ごとに、その要素を記述する対応するインタフェースと、その要素を作成するファクトリ・メソッドがあります。 要素によっては、対応するビルダーの(e.g., CodeBuilder.invokevirtual(ClassDesc, String, MethodTypeDesc)
)にも便利なメソッドがあります。
要素内のほとんどのシンボリック情報は、定数プール・エントリ(たとえば、フィールドの所有者はClassEntry
で表されます。)で表されます ファクトリおよびビルダーは、java.lang.constant
(例: ClassDesc
。)からの名目記述子も受け入れます
データ・モデル
各種類の要素は、名前、オプションのアリティ・インジケータ(ゼロ以上、ゼロまたは1つのみ)およびコンポーネントのリストによって定義します。 クラスの要素は、フィールド、メソッド、およびクラスに表示される属性です。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
の要素には、通常のバイトコードに加え、ブランチ・ターゲット、行番号メタデータ、ローカル変数メタデータおよびcatchブロックを表す多数の擬似命令が含まれます。
CodeElement = Instruction | PseudoInstruction
Instruction =
LoadInstruction(TypeKind type, int slot)
| StoreInstruction(TypeKind type, int slot)
| IncrementInstruction(int slot, int constant)
| BranchInstruction(Opcode opcode, Label target)
| LookupSwitchInstruction(Label defaultTarget, List<SwitchCase> cases)
| TableSwitchInstruction(Label defaultTarget, int low, int high,
List<SwitchCase> cases)
| ReturnInstruction(TypeKind kind)
| ThrowInstruction()
| FieldInstruction(Opcode opcode, FieldRefEntry field)
| InvokeInstruction(Opcode opcode, MemberRefEntry method, boolean isInterface)
| InvokeDynamicInstruction(InvokeDynamicEntry invokedynamic)
| NewObjectInstruction(ClassEntry className)
| NewReferenceArrayInstruction(ClassEntry componentType)
| NewPrimitiveArrayInstruction(TypeKind typeKind)
| NewMultiArrayInstruction(ClassEntry componentType, int dims)
| ArrayLoadInstruction(Opcode opcode)
| ArrayStoreInstruction(Opcode opcode)
| TypeCheckInstruction(Opcode opcode, ClassEntry className)
| ConvertInstruction(TypeKind from, TypeKind to)
| OperatorInstruction(Opcode opcode)
| ConstantInstruction(ConstantDesc constant)
| StackInstruction(Opcode opcode)
| MonitorInstruction(Opcode opcode)
| NopInstruction()
PseudoInstruction =
| LabelTarget(Label label)
| LineNumber(int line)
| ExceptionCatch(Label tryStart, Label tryEnd, Label handler, ClassEntry exception)
| LocalVariable(int slot, 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
-
クラス説明クラス、メソッドまたはフィールドのアクセス・フラグをモデル化します。JVMS 4.7.16.1で定義されているように、
element_value
構造、または注釈の要素と値のペアの値をモデル化します。要素と値のペアの注釈値をモデル化します。要素と値のペアの配列値をモデル化します。要素と値のペアのブール値をモデル化します。要素と値のペアのバイト値をモデル化します。要素と値のペアのchar値をモデル化します。要素と値のペアのクラス値をモデル化します。要素と値のペアの定数値をモデル化します。要素と値のペアの二重値をモデル化します。要素と値のペアの列挙値をモデル化します。要素と値のペアの浮動小数点値をモデル化します。要素と値のペアのint値をモデル化します。要素と値のペアの長い値をモデル化します。要素と値のペアの短い値をモデル化します。要素と値のペアの文字列値をモデル化します。class
ファイル形式で属性(JVMS 4.7)をモデル化します。class
ファイル、フィールド、メソッド、Code
属性、レコード・コンポーネントなどの属性を持つclass
ファイル構造を記述するClassFileElement
。AttributeMapper<A extends Attribute<A>>属性のclass
ファイル表現とそのAPIモデルの間の双方向マッパー。属性のclass
ファイル表現のデータ依存性を示します。事前定義済の(JVMS 4.7)およびJDK固有の非標準属性の属性マッパー。ブートストラップ・メソッド表のエントリをモデル化します。AttributeMapper
の高度なclass
ファイル書込みサポート。class
ファイルのビルダー。ClassModel
のメンバー要素のマーカー・インタフェース。class
ファイルを解析、変換および生成する機能を提供します。class
ファイルを解析するためのユーザー定義属性について説明するオプション。変換後に正確性を検証できない属性を保持するか破棄するかを示すオプションです。スタック・マップの生成時またはクラスの検証時に使用するクラス階層リゾルバを説明するオプション。class
ファイルの変換時に元の定数プールから拡張するかどうかを示すオプション。スタック・マップ生成にアクセスできないコードにパッチを適用するかどうかを示すオプション。「バインドされていないラベル」をフィルタし、可能な場合はその包含構造を削除するかどうかを示すオプション。CodeModel
またはCodeBuilder
のトラバースでデバッグPseudoInstruction
を処理するか破棄するかを指定するオプション。CodeModel
またはCodeBuilder
のトラバースでLineNumber
を処理するか破棄するかを示すオプション。class
ファイルの解析または書込みに影響するオプション。必要に応じて、短いジャンプを自動的に同等の指示に書き換えるかどうかを示すオプション。スタック・マップを生成するかどうかを説明するオプションです。ClassFileBuilder<E extends ClassFileElement, B extends ClassFileBuilder<E,B>> CompoundElement
のビルダー。ビルドされた構造体に統合されるメンバー要素を受け入れます。class
ファイル形式の特殊機能を持つ構造体のマーカー・インタフェース。ClassFileTransform<C extends ClassFileTransform<C,E, B>, E extends ClassFileElement, B extends ClassFileBuilder<E, B>> class
ファイル(JVMS 4.1)のマイナーおよびメジャー・バージョン番号をモデル化します。「スタック・マップの生成」および「確認」のクラス階層情報を提供します。解決済みクラスに関する情報。class
ファイルをモデル化します。AttributeMapper
の高度なclass
ファイル読取りサポート。JVMS 4.7.9.1で定義されているクラスまたはインタフェースの汎用シグネチャをモデル化します。ClassElement
のストリームに対する変換。Code
属性(メソッド本体)のビルダー。コードのブロック用のビルダー。catchブロックを追加するビルダー。CodeModel
のメンバー要素のマーカー・インタフェース。メソッド(Code
属性)の本文をモデル化します。CodeElement
のストリームに対する変換。CompoundElement<E extends ClassFileElement>メンバー構造の構成とみなすことができるclass
ファイル構造。CustomAttribute<T extends CustomAttribute<T>>class
ファイル内のユーザー定義属性をモデル化します。フィールドのビルダー。FieldModel
のメンバー要素のマーカー・インタフェース。フィールドをモデル化します。FieldElement
のストリームに対する変換。メソッドのCode
属性のcode
配列内の実行可能命令をモデル化します。クラスのインタフェース(JVMS 4.1)をモデル化します。メソッド本文の命令内の位置のマーカー。メソッドのビルダー。MethodModel
のメンバー要素のマーカー・インタフェース。メソッドをモデル化します。JVMS 4.7.9.1で定義されているメソッドまたはコンストラクタの汎用シグネチャをモデル化します。MethodElement
のストリームに対する変換。JVMS 6.5で説明されているように、JVM命令セットのopコードについて説明します。opcodesの種類JVMS 4.7.9.1で定義されている一般的なJava型のシグネチャをモデル化します。配列型のシグネチャをモデル化します。プリミティブ型(JLS 4.2)またはvoidのシグネチャをモデル化します。パラメータ化された可能性があるクラスまたはインタフェース型のシグネチャをモデル化します。参照型(クラス、インタフェース、型変数または配列型)のシグネチャをモデル化します。throwable型のシグネチャのマーカー・インタフェース。型パラメータの引数である型引数をモデル化します。明示的なバインド型を持つ型引数をモデル化します。型引数のワイルドカード・インジケータをモデル化します。Javaプログラムで無制限のワイルドカード型引数*
または?
をモデル化します。「type変数」を導入する汎用クラス、インタフェース、メソッドまたはコンストラクタの型パラメータのシグネチャをモデル化します。型変数のシグネチャをモデル化します。クラスのスーパークラス(JVMS 4.1)をモデル化します。type_annotation
構造(JVMS 4.7.20)をモデル化します。例外パラメータ宣言のi番目の型に注釈が表示されることを示します。フィールド宣言の型、メソッドの戻り型、新しく構築されたオブジェクトの型、またはメソッドまたはコンストラクタの受信側型に注釈が表示されることを示します。メソッド、コンストラクタまたはラムダ式の仮パラメータ宣言で、型に注釈が表示されることを示します。try-with-resources文でリソースとして宣言された変数を含む、ローカル変数宣言の型に注釈が表示されることを示します。ローカル変数が値を持つコード配列オフセットの範囲と、そのローカル変数が見つかる現在のフレームのローカル変数配列へのインデックスを示します。注釈が、instanceof式の型または新しい式、またはメソッド参照式の::の前の型のいずれかに出現することを示します。クラス宣言またはインタフェース宣言のextends句またはimplements句の型に注釈が表示されることを示します。宣言または式のどの型に注釈を付けるかを指定します。JVMS 4.7.20.1で定義されている、注釈が表示されるターゲットの種類。メソッド宣言またはコンストラクタ宣言のthrows句のi番目の型に注釈が表示されることを示します。注釈がキャスト式のi番目の型、または次のいずれかの明示的な型引数リストのi番目の型引数に表示されることを示します: 新しい式、明示的なコンストラクタ呼出し文、メソッド呼出し式またはメソッド参照式。注釈が、汎用クラス、インタフェース、メソッドまたはコンストラクタのj番目の型パラメータ宣言のi番目の境界に表示されることを示します。注釈が、汎用クラス、汎用インタフェース、汎用メソッドまたは汎用コンストラクタのi番目の型パラメータの宣言に表示されることを示します。JVMS: Type_path構造は、JVMS 4.7.20.2で定義されているように、型のどの部分に注釈を付けるかを識別タイプ・パスの種類(JVMS 4.7.20.2で定義)Java Virtual Machineが操作するデータ型について説明します。