モジュール java.base
パッケージ java.lang.invoke

クラスVarHandle

java.lang.Object
java.lang.invoke.VarHandle
すべての実装されたインタフェース:
Constable

public abstract sealed class VarHandle extends Object implements Constable
VarHandleは、変数に対する、あるいはパラメータを用いて指定される変数の一群に対する、動的に強く型付けされた参照です。それら変数には、staticなフィールド、非staticなフィールド、配列の要素、ヒープの外側にあるデータ構造のコンポーネントなどが含まれます。 このような変数へのアクセスは、プレーン読取り/書込みアクセス、揮発性読取り/書込みアクセス、比較/設定など、様々な「アクセス・モード」でサポートされています。

VarHandlesは不変であり、可視状態はありません。 VarHandlesは、ユーザーがサブクラス化できません。

VarHandleには次のものがあります:

  • variable type T、このVarHandleによって参照されるすべての変数の型
  • coordinate types CT1, CT2, ..., CTnのリスト。このVarHandleによって参照される変数を共同で検索する「座標式」のタイプ。
変数型および座標型はプリミティブ型または参照型であり、Classオブジェクトで表されます。 座標タイプのリストは空である可能性があります。

lookup VarHandleインスタンスを生成するファクトリ・メソッドは、サポートされている変数型と座標型のリストを文書化します。

各アクセス・モードは、アクセス・モード用に指定された「シグネチャ・ポリモーフィック」メソッドである1つの「アクセス・モード方式」に関連付けられます。 VarHandleインスタンスでアクセス・モード・メソッドが呼び出されると、呼出しの最初の引数は、変数が正確にアクセスされるオブジェクトを示す座標式になります。 呼出しの末尾の引数は、アクセス・モードにとって重要な値を表します。 たとえば、様々な比較/設定または比較/交換アクセス・モードでは、変数の予期される値と新しい値に2つの末尾の引数が必要です。

アクセス・モード・メソッドの呼出しの引数および引数の型は、静的にチェックされません。 かわりに、各アクセス・モード・メソッドは、MethodTypeのインスタンスとして表されるaccess mode typeを指定します。このメソッドは、引数を動的にチェックするメソッド・シグネチャの一種として機能します。 アクセス・モード・タイプは、VarHandleインスタンスの座標型およびアクセス・モードの重要度の値の型に関する仮パラメータ型を提供します。 アクセス・モード・タイプでは、戻り型も与えられます。多くの場合、VarHandleインスタンスの変数型です。 アクセス・モード・メソッドがVarHandleインスタンスで呼び出される場合、コール・サイトのシンボリック・タイプ記述子、呼出しの引数の実行時タイプおよび戻り値のランタイム・タイプは、アクセス・モード・タイプで指定された型をmatchする必要があります。 一致が失敗すると、実行時例外がスローされます。 たとえば、アクセス・モード・メソッドcompareAndSet(java.lang.Object...)は、受信側が座標型CT1, ..., CTnおよび変数型Tを持つVarHandleインスタンスである場合、そのアクセス・モード・タイプは(CT1 c1, ..., CTn cn, T expectedValue, T newValue)booleanであることを指定します。 VarHandleインスタンスが配列要素にアクセスでき、その座標型がString[]およびintで、その変数型がStringであるとします。 このVarHandleインスタンスのcompareAndSetのアクセス・モード型は(String[] c1, int c2, String expectedValue, String newValue)booleanになります。 このようなVarHandleインスタンスは、array factory methodによって生成され、次のように配列要素にアクセスします:

 
 String[] sa = ...
 VarHandle avh = MethodHandles.arrayElementVarHandle(String[].class);
 boolean r = avh.compareAndSet(sa, 10, "expected", "new");
 

アクセス・モードは、アトミック性と一貫性のプロパティを制御します。 「プレーン」 read (get)およびwrite (set)アクセスは、参照および最大32ビットのプリミティブ値のビット単位のアトミックであることが保証され、実行スレッド以外のスレッドに関しては観察可能な順序制約がありません。 「不透明」演算は、同じ変数へのアクセスに関してビット単位のアトムであり、コヒーレントに順序付けられます。 Opaqueプロパティに従うことに加えて、「獲得」モードの読み込みとそれに続くアクセスは、「リリース」モードの書き込みと以前のアクセスの一致後に順序付けられます。 AcquireおよびReleaseプロパティに従うことに加えて、すべての「揮発性」操作は、互いに完全に順序付けられています。

アクセス・モードは、次のカテゴリに分類されます:

lookup VarHandleインスタンスを生成またはlookupするファクトリ・メソッドでは、サポートされるアクセス・モードのセットが文書化されます。これには、変数型および変数が読取り専用かどうかに基づく文書化の制限も含まれる場合があります。 アクセス・モードがサポートされていない場合、対応するアクセス・モード・メソッドは呼び出し時にUnsupportedOperationExceptionをスローします。 ファクトリ・メソッドは、アクセス・モード・メソッドによってスローされる可能性がある、宣言されていないその他の例外を文書化する必要があります。 getアクセス・モードはすべてのVarHandleインスタンスでサポートされており、対応するメソッドは決してUnsupportedOperationExceptionをスローしません。 VarHandleが読取り専用変数(たとえば、finalフィールド)を参照する場合、書込み、アトミック更新、数値アトミック更新およびビット単位アトミック更新アクセス・モードがサポートされず、対応するメソッドがUnsupportedOperationExceptionをスローします。 読取り/書込みアクセス・モード(サポートされている場合) (getおよびsetを除く)は、参照型およびすべてのプリミティブ型に対するアトミック・アクセスを提供します。 ファクトリ・メソッドのドキュメントで特に明記されていないかぎり、アクセス・モードgetおよびset (サポートされている場合)は、32ビット・プラットフォームではlongおよびdoubleを除き、参照型およびすべてのプリミティブ型にアトミック・アクセスを提供します。

アクセス・モードは、変数の宣言サイトで指定したメモリー順序付け効果をオーバーライドします。 たとえば、getアクセス・モードを使用してフィールドにアクセスするVarHandleは、そのフィールドがvolatileと宣言されていても、指定された「アクセス・モード」としてフィールドにアクセスします。 Javaメモリー・モデルでは驚くべき結果が得られる可能性があるため、混合アクセスを実行する場合は細心の注意を払う必要があります。

様々なアクセス・モードでの変数へのアクセスのサポートに加えて、メモリー・フェンス・メソッドと呼ばれる一連の静的メソッドも、メモリー順序付けをきめ細かく制御するために提供されます。 Java言語仕様では、他のスレッドが、ロックの使用、volatileフィールドまたはVarHandlesなどから生じる制約に従って、プログラム・ソース・コードとは異なる順序で実行されたかのように操作を監視できます。 fullFenceacquireFencereleaseFenceloadLoadFenceおよびstoreStoreFenceという静的メソッドを使用して制約を適用することもできます。 これらの仕様は、特定のアクセス・モードの場合と同様に、"reorderings"がないという点で表現されます -- フェンスが存在しない場合に発生する可能性のある観察可能な順序効果。 アクセス・モード・メソッドおよびメモリー・フェンス・メソッドの仕様のより正確な表現は、Java言語仕様の今後の更新に伴う可能性があります。

アクセス・モード・メソッドの呼び出しのコンパイル

アクセス・モード・メソッドに名前を付けるJavaメソッド・コール式は、Javaソース・コードからVarHandleを呼び出すことができます。 ソース・コードの観点から、これらのメソッドは任意の引数を取ることができ、その多相結果(表現されていれば)は任意の戻り型にキャストできます。 正式には、アクセス・モード・メソッドに可変引数ObjectおよびObject戻り型(戻り値の型がポリモーフィックである場合)を付与することによって実現されますが、この呼出しの自由度をJVM実行スタックに直接接続する「シグネチャ・ポリモーフィック」という追加の品質があります。

仮想メソッドの場合と同様に、アクセス・モード・メソッドに対するソース・レベルのコールは、invokevirtual命令にコンパイルされます。 通常とは異なる点として、コンパイラは実際の引数の型を記録する必要があり、引数に対するメソッド呼出し変換を実行することができません。 代わりに、独自の変換されていない型に従ってスタックにプッシュする命令を生成する必要があります。 VarHandleオブジェクト自体は、引数の前にスタックにプッシュされます。 その後、コンパイラは、引数および戻り型を記述するシンボリック型記述子を使用してアクセス・モード・メソッドを呼び出すinvokevirtual命令を生成します。

完全なシンボリック型記述子を発行するには、コンパイラは戻り型(ポリモーフィックの場合)も決定する必要があります。 これは、メソッド呼出し式(ある場合)のキャスト、または呼出しが式の場合はObject、または呼出しが文の場合はvoidに基づきます。 キャスト先はプリミティブ型でもかまいません(ただしvoidは不可)。

特殊なケースとして、キャストされていないnull引数にはjava.lang.Voidのシンボリック型記述子が与えられます。 Void型の参照はnull参照以外には存在しないため、型Voidのあいまいさが問題になることはありません。

アクセス・モード・メソッドの呼び出しの実行

invokevirtual命令は、最初に実行されたときに、命令内の名前を象徴的に解決し、メソッド・コールが静的に有効であることを検証することによってリンクされます。 これは、アクセス・モード・メソッドの呼出しにも適用されます。 この場合、コンパイラによって出力されるシンボリック型記述子が正しい構文かどうかチェックされ、そこに含まれる名前が解決されます。 したがって、シンボリック型記述子が構文的に整形式であり、型が存在していれば、アクセス・モード・メソッドを呼び出すinvokevirtual命令は常にリンクされます。

リンク後にinvokevirtualが実行されると、受信側VarHandleのアクセス・モード・タイプがJVMによって最初にチェックされ、シンボリック・タイプ記述子と一致していることが確認されます。 型の一致が失敗した場合、呼出し側が呼び出しているアクセス・モード・メソッドが、呼び出される個々のVarHandleに存在しないことを意味します。

アクセス・モード・メソッドの起動は、デフォルトではMethodHandle.invoke(java.lang.Object...)の起動と同様に動作し、受信側メソッド・ハンドルはVarHandleインスタンスを先頭の引数として受け入れます。 具体的には、次のようになります。ここで、{access-mode}はアクセス・モード・メソッド名に対応します:

 
 VarHandle vh = ..
 R r = (R) vh.{access-mode}(p1, p2, ..., pN);
 
次のように動作します:
 
 VarHandle vh = ..
 VarHandle.AccessMode am = VarHandle.AccessMode.valueFromMethodName("{access-mode}");
 MethodHandle mh = MethodHandles.varHandleExactInvoker(
                       am,
                       vh.accessModeType(am));

 R r = (R) mh.invoke(vh, p1, p2, ..., pN)
 
(モジュロ・アクセス・モード・メソッドは、Throwableのスローを宣言しません)。 これは、次の操作に相当します。
 
 MethodHandle mh = MethodHandles.lookup().findVirtual(
                       VarHandle.class,
                       "{access-mode}",
                       MethodType.methodType(R, p1, p2, ..., pN));

 R r = (R) mh.invokeExact(vh, p1, p2, ..., pN)
 
必要なメソッド型がシンボリック型記述子で、MethodHandle.invokeExact(java.lang.Object...)が実行されます。これは、ターゲットの呼出しの前に、asType (MethodHandles.varHandleInvoker(java.lang.invoke.VarHandle.AccessMode, java.lang.invoke.MethodType)も参照してください)のように、ハンドルは必要に応じて参照キャストを適用し、ボックス化、ボックス化解除またはプリミティブ値を拡大するためです。 このような動作は、より簡潔には次のようになります:
 
 VarHandle vh = ..
 VarHandle.AccessMode am = VarHandle.AccessMode.valueFromMethodName("{access-mode}");
 MethodHandle mh = vh.toMethodHandle(am);

 R r = (R) mh.invoke(p1, p2, ..., pN)
 
この場合、メソッド・ハンドルはVarHandleインスタンスにバインドされます。

VarHandle呼出し動作は、アクセス・モード・メソッドの呼出しがMethodHandle.invokeExact(java.lang.Object...)の呼出しのように動作するように調整できます。(withInvokeExactBehavior()を参照してください)では、受信側のメソッド・ハンドルがVarHandleインスタンスを先頭の引数として受け入れます。 具体的には、次のようになります。ここで、{access-mode}はアクセス・モード・メソッド名に対応します:

 
 VarHandle vh = ..
 R r = (R) vh.{access-mode}(p1, p2, ..., pN);
 
次のように動作します:
 
 VarHandle vh = ..
 VarHandle.AccessMode am = VarHandle.AccessMode.valueFromMethodName("{access-mode}");
 MethodHandle mh = MethodHandles.varHandleExactInvoker(
                       am,
                       vh.accessModeType(am));

 R r = (R) mh.invokeExact(vh, p1, p2, ..., pN)
 
(モジュロ・アクセス・モード・メソッドは、Throwableのスローを宣言しません)。 このような動作は、より簡潔には次のようになります:
 
 VarHandle vh = ..
 VarHandle.AccessMode am = VarHandle.AccessMode.valueFromMethodName("{access-mode}");
 MethodHandle mh = vh.toMethodHandle(am);

 R r = (R) mh.invokeExact(p1, p2, ..., pN)
 
この場合、メソッド・ハンドルはVarHandleインスタンスにバインドされます。

呼出しのチェック

一般的なプログラムでは、通常、VarHandleアクセス・モード・タイプの一致は成功します。 ただし、一致が失敗した場合、JVMはWrongMethodTypeExceptionをスローします。

したがって、静的に型指定されるプログラムでリンク・エラーとして表示されるアクセス・モード・タイプの不一致は、VarHandlesを使用するプログラムで動的WrongMethodTypeExceptionとして表示されることがあります。

アクセス・モード・タイプには"live" Classオブジェクトが含まれているため、メソッド・タイプ一致では型名とクラス・ローダーの両方が考慮されます。 したがって、あるクラス・ローダーL1でVarHandle VHが作成され、別のL2で使用されている場合でも、L2で解決されるコール元のシンボリック・タイプ記述子は、L1で解決される元のコール先メソッドのシンボリック・タイプ記述子と照合されるため、VarHandleアクセス・モード・メソッドの呼出しは型安全です。 L1の解決は、VHが作成され、そのアクセス・モード・タイプが割り当てられるときに行われますが、L2の解決は、invokevirtual命令がリンクされると行われます。

タイプ記述子チェックとは別に、変数にアクセスするためのVarHandles機能は制限されません。 VarHandleが非public変数に対して、その変数にアクセスできるクラスによって形成される場合、結果のVarHandleは、その変数への参照を受け取る呼出し側がどこからでも使用できます。

リフレクション・メソッドが呼び出されるたびにアクセスがチェックされるコア・リフレクションAPIとは異なり、VarHandleアクセス・チェックは「VarHandleが作成されたとき」で実行されます。 したがって、非public変数へのVarHandles、または非publicクラスの変数へのVarHandlesは、通常、秘密にしておく必要があります。 信頼できないコードがそれらを使用しても問題が発生しない場合を除き、それらを信頼できないコードに渡さないようにしてください。

VarHandleの作成

Javaコードでは、そのコードにアクセスできるすべてのフィールドに直接アクセスするVarHandleを作成できます。 これは、MethodHandles.Lookupというリフレクティブな機能ベースのAPIを介して行われます。 たとえば、非静的フィールドのVarHandleは、Lookup.findVarHandleから取得できます。 また、コア・リフレクションAPIオブジェクトLookup.unreflectVarHandleからの変換メソッドもあります。

保護されたフィールド・メンバーへのアクセスは、アクセス・クラスまたはそのサブクラスの1つだけのレシーバに制限され、アクセス・クラスは、保護されているメンバーの定義クラスのサブクラス(パッケージ兄弟)である必要があります。 VarHandleが現在のパッケージ外の宣言クラスの保護された非静的フィールドを参照する場合、receiver引数はアクセス・クラスのタイプに絞り込まれます。

VarHandlesとCore Reflection APIの相互運用

Lookup APIでファクトリ・メソッドを使用すると、コア・リフレクションAPIオブジェクトで表されるすべてのフィールドを、動作的に同等のVarHandleに変換できます。 たとえば、リフレクティブFieldは、Lookup.unreflectVarHandleを使用してVarHandleに変換できます。 結果のVarHandlesは、通常、基礎となるフィールドへのより直接的で効率的なアクセスを提供します。

特殊なケースとして、Core Reflection APIを使用して、このクラスのシグネチャ多相アクセス・モード・メソッドを表示すると、それらは通常の非多相メソッドとして表示されます。 Class.getDeclaredMethodで示されるそれらのリフレクション表現は、このAPIでの特殊なステータスの影響を受けません。 たとえば、Method.getModifiersでは、同様に宣言されたすべてのメソッドで必要になる修飾子ビット(この場合はnativeビットやvarargsビットなど)が厳密に報告されます。

リフレクトされたメソッドと同様に、これらのメソッド(反射したとき)は、JNIを介して直接、またはLookup.unreflectを介して間接的にjava.lang.reflect.Method.invokeを介して呼び出すことができます。 ただし、このようなリフレクティブ・コールでは、アクセス・モード・メソッドの呼出しは行われません。 そのような呼出しに必要な引数(Object[]型の単一の引数)を渡してもその引数は無視され、UnsupportedOperationExceptionがスローされます。

invokevirtual命令は、任意のシンボリック・タイプ記述子でVarHandleアクセス・モード・メソッドをネイティブに呼び出すことができるため、このリフレクティブ・ビューは、バイトコードを介してこれらのメソッドの通常の表示と競合します。 したがって、これらのネイティブ・メソッドは、Class.getDeclaredMethodによって反射的に表示された場合、プレースホルダーとしてのみ考慮されます。

特定のアクセス・モード・タイプの実行者メソッドを取得するには、MethodHandles.varHandleExactInvoker(java.lang.invoke.VarHandle.AccessMode, java.lang.invoke.MethodType)またはMethodHandles.varHandleInvoker(java.lang.invoke.VarHandle.AccessMode, java.lang.invoke.MethodType)を使用します。 Lookup.findVirtual APIは、指定されたアクセス・モード・タイプのアクセス・モード・メソッドをコールするメソッド・ハンドルを返すこともでき、これはMethodHandles.varHandleInvoker(java.lang.invoke.VarHandle.AccessMode, java.lang.invoke.MethodType)と同様の動作をします。

VarHandlesとJava汎用の相互運用

VarHandleは、Java汎用型で宣言されたフィールドなどの変数に対して取得できます。 Core Reflection APIと同様に、VarHandleの変数型はソース・レベル型の消去から構築されます。 VarHandleアクセス・モード・メソッドが呼び出されると、その引数の型または戻り値のキャスト型は、汎用型または型インスタンスになります。 これが起こった場合、コンパイラは、invokevirtual命令のシンボリック型記述子を構築する際に、それらの型を対応するイレイジャで置き換えます。
導入されたバージョン:
9
関連項目: