モジュール java.base

パッケージjava.lang.classfile


パッケージjava.lang.classfile
java.lang.classfileは、JavaプラットフォームのプレビューAPIです。
プレビュー機能が有効な場合のみ、プログラムでjava.lang.classfileを使用できます。
プレビュー機能は、今後のリリースで削除するか、Javaプラットフォームの永続機能にアップグレードすることができます。

クラス・ファイルの解析、生成および変換ライブラリを提供します。

java.lang.classfileパッケージには、「Java Java Virtual Machine仕様」の第4章で指定されている、Javaクラス・ファイルの読取り、書込みおよび変更のためのクラスが含まれています。

クラス・ファイルの読取り中

クラス・ファイルを読み取るためのメイン・クラスはClassModelPREVIEWで、ClassFile.parse(byte[])PREVIEWを使用してバイトをClassModelPREVIEWに変換します:

ClassModel cm = ClassFile.of().parse(bytes);

parseには、様々な処理オプションを指定できるオーバーロードがいくつか追加されています。

ClassModelPREVIEWは、クラス・ファイルの不変の説明です。 クラス・メタデータ(e.g., ClassModel.thisClass()PREVIEW, ClassModel.flags()PREVIEW)および下位クラス・ファイル・エンティティ(ClassModel.fields()PREVIEW, AttributedElement.attributes()PREVIEW)で取得するアクセッサ・メソッドを提供します。 ClassModelPREVIEWは遅延して膨張します。クラス・ファイルのほとんどの部分は、実際に必要になるまで解析されません。

次の方法で、クラスのフィールドおよびメソッドの名前を列挙できます。

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

メソッドを列挙すると、メソッドごとにMethodModelPREVIEWが取得されます。ClassModelのように、メソッド・メタデータへのアクセスと、メソッド本文のバイトコードなどの下位エンティティの子孫になる機能が提供されます。 このようにして、ClassModelはツリーのルートであり、フィールド、メソッドおよび属性の子を持ち、MethodModelは独自の子(attributes, CodeModel, etc.)を持ちます。

ClassModel.methods()PREVIEWのようなメソッドを使用すると、クラス構造を明示的に横断し、目的の部分に直接移動できます。 これは特定の種類の分析に役立ちますが、クラス・ファイル全体を処理する場合は、より整理されたものが必要な場合があります。 ClassModelPREVIEWは、クラス・ファイルのビューを一連のクラス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()PREVIEWアクセッサなど)を介して、またはelementsの線形シーケンスとして探索できます。 (要素はモデルでもあり、FieldModelPREVIEWはクラスの要素でもあります。) モデル・タイプ(e.g., MethodModelPREVIEW)ごとに、対応する要素タイプ(MethodElementPREVIEW)があります。 モデルと要素は不変であり、遅延して膨張するため、モデルの作成では必ずしもコンテンツ全体を処理する必要はありません。

定数プール

クラス・ファイルの興味深いコンテンツの多くは、「定数プール」にあります。 ClassModelPREVIEWは、ClassModel.constantPool()PREVIEWを介して定数プールのレイジー・インフレーションされた読取り専用ビューを提供します。 クラス・ファイル・コンテンツの説明は、多くの場合、ClassEntryPREVIEWUtf8EntryPREVIEWなど、PoolEntryPREVIEWの様々なサブタイプの形式で公開されます。

定数プール・エントリもモデルおよび要素を介して公開されます。前述のトラバース例では、InvokeInstructionPREVIEW要素によって、定数プール内の Constant_Class_infoエントリに対応するownerのメソッドが公開されています。

属性

クラス・ファイルの内容の多くは属性に格納され、属性はクラス、メソッド、フィールド、レコード・コンポーネントおよびCode属性にあります。 ほとんどの属性は要素として表示されます。たとえば、SignatureAttributePREVIEWClassElementPREVIEWMethodElementPREVIEWおよびFieldElementPREVIEWです。これは、これらのすべての場所に出現し、対応するモデルの要素の反復時に含まれるためです。

一部の属性は要素として表示されません。これらは密接に結合された属性です。 -- 論理的な部分 -- クラス・ファイルの他の部分。 これには、BootstrapMethods, LineNumberTable, StackMapTable, LocalVariableTable属性および LocalVariableTypeTable属性が含まれます。 これらはライブラリによって処理され、(BootstrapMethods属性のエントリは定数プールの一部として扱われ、行番号およびローカル変数メタデータはCodeModelPREVIEWの要素としてモデル化されます。)と結合された構造の一部として処理されます

Code属性は、MethodElementPREVIEWとしてモデル化されるだけでなく、複雑な構造のため、独自の右(CodeModelPREVIEW)のモデルでもあります。

各標準属性にはインタフェース(in java.lang.classfile.attribute)があり、このインタフェースは属性の内容を公開し、属性を構成するためのファクトリを提供します。 たとえば、Signature属性はSignatureAttributePREVIEWクラスによって定義され、SignatureAttribute.signature()PREVIEWおよびUtf8EntryPREVIEWまたはStringを使用するファクトリのアクセッサを提供します。

カスタム属性

属性は、クラス・ファイル形式とそれに対応するオブジェクト形式の間に、AttributeMapperPREVIEWを介して変換されます。 AttributeMapperは、クラス・ファイル形式から属性インスタンスへのマッピング用のAttributeMapper.readAttribute(AttributedElement, ClassReader, int)PREVIEWメソッドと、クラス・ファイル形式へのマッピング用のAttributeMapper.writeAttribute(java.lang.classfile.BufWriter, java.lang.Object)PREVIEWメソッドを提供します。 また、属性名、属性が適用可能なクラス・ファイル・エンティティのセット、同じ種類の複数の属性を単一のエンティティで許可するかどうかなどのメタデータも含まれます。

「Java Virtual Machine仕様」のセクション4.7で定義されている属性タイプごとに組込み属性マッパー(in AttributesPREVIEW)と、JDKで使用される一般的な非標準属性(CharacterRangeTableなど)があります。

認識されない属性は、UnknownAttributePREVIEW型の要素として提供され、属性のbyte[]コンテンツにのみアクセスできます。

非標準属性の場合、ユーザー指定の属性マッパーは、ClassFile.AttributeMapperOption.of(java.util.function.Function)PREVIEW} クラス・ファイル・オプションを使用して指定できます。 カスタム属性の実装は、CustomAttributePREVIEWを拡張する必要があります。

オプション

ClassFile.of(java.lang.classfile.ClassFile.Option[])PREVIEWは、オプションのリストを受け入れます。 ClassFile.OptionPREVIEWは、静的に列挙されたいくつかのオプションのベース・インタフェースであり、次のようなより複雑なオプションのファクトリです:

ほとんどのオプションでは、デバッグ情報や認識されない属性など、トラバース中にクラス・ファイルの特定の部分をスキップするようにリクエストできます。 一部のオプションでは、スタック・マップなど、クラス・ファイルの一部の生成を抑制できます。 これらのオプションの多くは、パフォーマンスのトレードオフにアクセスすることであり、デバッグ情報および行番号の処理にはコスト(書くことと読むことの両方。)があります。 この情報が必要ない場合は、オプションで抑制してパフォーマンスを向上させることができます。

クラス・ファイルの書込み

ClassFileの生成は、buildersによって行われます。 モデルを持つエンティティ・タイプごとに、対応するビルダー・タイプもあります。クラスは、ClassBuilderPREVIEWMethodBuilderPREVIEWなどのメソッドを使用して構築されます。

ビルダーは、直接作成するのではなく、ユーザー指定のラムダへの引数として提供されます。 使い慣れた"こんにちは世界"プログラムを生成するには、クラス・ビルダーを要求し、そのクラス・ビルダーを使用してコンストラクタおよび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を使用すると、メソッド・ビルダー・カスタム・ラムダをスキップして、メソッド本体を直接構築するコード・ビルダーを作成するようにClassBuilderPREVIEWに依頼できます:

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.invokevirtualPREVIEWCodeBuilder.invokeInstructionPREVIEWまたはCodeBuilder.withPREVIEWを使用して生成できます。

コンビニエンス・メソッドCodeBuilder.invokevirtualは、コンビニエンス・メソッドCodeBuilder.invokeInstructionをコールするかのように動作し、次にメソッドCodeBuilder.withをコールするかのように動作します。 このビルダーでのメソッド・コールの構成により、変換(後で説明します)を作成できます。

シンボリック情報

クラスおよび型のシンボリック情報を記述するために、APIはClassDescMethodTypeDescなどのjava.lang.constantの名目記述子抽象を使用します。これは、RAW文字列を使用するよりもエラーの発生が少なくなります。

定数プール・エントリに名目表現がある場合、対応する名目記述子型を返すメソッドを提供します。たとえば、メソッドClassEntry.asSymbol()PREVIEWClassDescを返します。

適切なビルダーは、シンボル情報を持つ要素を構築するための2つのメソッドを提供します。1つは名目記述子を受け入れ、もう1つは定数プール・エントリを受け入れます。

一貫性チェック、構文チェック、検証

クラス・ファイル(null引数チェックを除く)の構築中または変換中に整合性チェックは実行されません。 すべてのビルダーおよびクラス・ファイル要素のファクトリ・メソッドは、暗黙的な検証なしで提供された情報を受け入れます。 ただし、致命的な不整合(例:無効なコード・シーケンスまたは未解決のラベル)は内部ツールに影響し、後でクラス・ファイルの構築プロセスで例外が発生する可能性があります。

名目記述子を使用すると、実際のコンテキストに基づいて、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)PREVIEWの呼出しによって実現できます。

クラス・ファイルの変換

ClassFile処理APIは、変換への読取りと書込みを組み合せるために最も頻繁に使用されます。この変換では、クラス・ファイルが読み取られ、ローカライズされた変更が行われますが、ほとんどのクラス・ファイルは変更されずに渡されます。 各種類のビルダーについて、XxxBuilderにはメソッドwith(XxxElement)があるため、変更せずに渡す要素をビルダーに直接戻すことができます。

名前が"debug"で始まるメソッドを削除する場合は、既存のClassModelPREVIEWを取得し、ClassBuilderPREVIEWを提供する新しいクラス・ファイルを作成し、元のClassModelPREVIEWの要素を反復して、削除するメソッドを除くすべてのメソッドをビルダーに渡すことができます:

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.transform(cc.parse(bytes), ct);

ClassTransform.droppingコンビニエンス・メソッドを使用すると、同じ変換構造を簡略化し、前述のように表現できます。

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

持ち上がる変革

変換を使用する例はわずかに短くなりますが、この方法で変換を表現する利点は、変換操作をより簡単に結合できることです。 Fooの静的メソッドの呼出しを、かわりにBarの対応するメソッドにリダイレクトするとします。 これは、CodeElementPREVIEWで変換として表すことができます:
CodeTransform fooToBar = (b, e) -> {
    if (e instanceof InvokeInstruction i
            && i.owner().asInternalName().equals("Foo")
            && i.opcode() == Opcode.INVOKESTATIC)
                b.invokeInstruction(i.opcode(), CD_Bar, i.name().stringValue(), i.typeSymbol(), i.isInterface());
    else b.with(e);
};

次に、コード要素に対するこの変換をメソッド要素の変換に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.invokeInstruction(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およびinstrumentCallsCodeTransform.andThen(java.lang.classfile.CodeTransform)PREVIEWで構成できます:

var cc = ClassFile.of();
byte[] newBytes = cc.transform(cc.parse(bytes),
                               ClassTransform.transformingMethods(
                                   MethodTransform.transformingCode(
                                       fooToBar.andThen(instrumentCalls))));
変換instrumentCallsは、変換forToBarによって生成されたすべてのコード要素(元のクラス・ファイルからのコード要素または置換(静的呼出しをFooBarに置換))を受け取ります。

定数プール共有

変換は、単に読取り、要素の変換および書込みのロジスティクスを処理するものではありません。 クラス・ファイルを変換するほとんどの場合、比較的小さな変更を行っています。 このようなケースを最適化するために、変換では、元のクラス・ファイルからの定数プールのコピーを使用して新しいクラス・ファイルがシードされます。これにより、(変換されないメソッドおよび属性は、バイトを解析してコンテンツを再生成するのではなく、バイトを一括コピーすることで処理できます。)が大幅に最適化されます。 定数プール共有が不要な場合は、ClassFile.ConstantPoolSharingOptionPREVIEWオプションで抑制できます。 このような抑制は、変換によって多くの要素が削除され、参照されない定数プール・エントリが多くなる場合に有益です。

不明なクラス・ファイル要素の変換処理

カスタムのクラス・ファイル変換では、将来の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(java.lang.constant.ClassDesc, java.lang.String, java.lang.constant.MethodTypeDesc)PREVIEW)にもコンビニエンス・メソッドがあります。

要素内のほとんどのシンボリック情報は、定数プール・エントリ(たとえば、フィールドの所有者はClassEntryPREVIEWで表されます。)で表されます ファクトリおよびビルダーは、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*
フィールドとメソッドは、独自の要素を持つモデルです。 フィールドとメソッドの要素はかなり単純です。メソッドの複雑さのほとんどは、CodeModelPREVIEW (Code属性をコード関連属性とともにモデル化する: スタック・マップ表、ローカル変数表、行番号表など)にあります
FieldElement =
    DeclarationElement
    | ConstantValueAttribute?(ConstantValueEntry constant)

MethodElement =
    DeclarationElement
    | CodeModel?()
    | AnnotationDefaultAttribute?(ElementValue defaultValue)
    | MethodParametersAttribute?(List<MethodParameterInfo> parameters)
    | ExceptionsAttribute?(List<ClassEntry> exceptions)
CodeModelPREVIEWは、その要素が「オーダー済み」であるという点で一意です。 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)

導入されたバージョン:
22