Skip to content

Instantly share code, notes, and snippets.

@matarillo
Last active April 11, 2020 01:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matarillo/eac3894c114a55d23177ad2e57c0ac91 to your computer and use it in GitHub Desktop.
Save matarillo/eac3894c114a55d23177ad2e57c0ac91 to your computer and use it in GitHub Desktop.
Translation using translate.google.com and www.deepl.com

http://cr.openjdk.java.net/~briangoetz/valhalla/sov/02-object-model.html

State of Valhalla

セクション2:言語モデル

Brian Goetz, Dec 2019

このドキュメントは、インライン型を組み込むための言語モデルについて説明します。 インライン型のJVMモデル、およびJavaソースコードからJavaクラスファイルへの変換戦略については、別のドキュメントで説明する予定です。 (このドキュメントでは、「現在」という言葉を、この言語の今の状況、すなわちインライン型がない状態を指すものとして使います。)

今の我々の位置

現在、型はプリミティブ型と参照型に分けられています。 8つの組み込みプリミティブ型があります(voidは型ではありません)。 参照型とは、プリミティブ型ではない型で、クラスまたはインターフェースとして宣言された型と、 配列型(String[], int[])および参照型のパラメーター化(List<String>, List<?>)などの非宣言型を含んでいます。

参照型とプリミティブ型は、考えられるほとんどすべての点で異なります。 参照型はメンバー(メソッドとフィールド)とスーパータイプ(スーパークラスとインターフェース)を持ち、すべてが(直接または間接的に)Objectを拡張します。 プリミティブ型にはメンバーがなく、型システム上の「孤島」であり、スーパータイプやサブタイプはありません。 プリミティブ型を参照型に接続するために、各プリミティブ型はラッパー型に関連付けられています(Integerintのラッパー型です)。 ラッパー型は参照型であるため、メンバーを持つことができ、サブタイピングに参加できます。 プリミティブ型とそれに対応するラッパー型の間には、ボクシングアンボクシングの変換があります。

Types, current world

値集合

すべての型は値集合を持ちます。これは、その型の変数に格納できる値の集合です。 (たとえば、intのようなプリミティブの値集合は32ビット整数の集合です。) 型Tの値集合を表すために、Vals(T) と記述することにします。 型Tが型Uのサブタイプである場合、Vals(T) ⊆ Vals(U) です。

オブジェクト はクラスのインスタンスです。 現在、すべてのオブジェクトには一意の オブジェクトアイデンティティ があります。 参照型の値集合は オブジェクト ではなく、 オブジェクトへの参照 で構成されます。 String型の変数がとりうる値は、 Stringオブジェクト自体ではなく、それらのStringオブジェクトへの参照です。 (経験豊富なJava開発者でさえ、オブジェクトを直接保存、操作、またはアクセスできないことに驚くかもしれません。 私たちはオブジェクト参照を扱うことに慣れすぎているので、違いに気付かないことさえあります。 実際、Javaオブジェクトが値渡しされるのか参照渡しされるのか、というのはよくある「落とし穴」の質問であり、答えは「どちらでもない」です。 オブジェクト参照値渡しされます。)

プリミティブ型の値集合は、プリミティブ値で構成されます(nullは含まれません)。
参照型の値集合は、オブジェクトインスタンスへの参照、またはnullで構成されます。

前の段落の2つの重要な事実(すべてのオブジェクトには一意のアイデンティティがあり、オブジェクトを操作する唯一の方法は参照を介すること)は両方とも、インライン型を取り込むと変わります。

次の図では、Javaプログラムの変数に格納できる値を強調表示するために、表現可能な値を赤いボックスで示します。

Values, current world

現在の世界における値の宇宙は、プリミティブ値とオブジェクトへの参照で構成されています。

現在の世界を要約すると、以下の通りです。

  • 型は、プリミティブ型と参照型に分かれています。
  • 参照型は、プリミティブ型ではなく、宣言されたクラス、宣言されたインターフェース、および配列型を含みます。
  • プリミティブには対応するラッパー型があり、そしてそれは参照型であり、プリミティブ型とそれに対応するラッパーの間にはボクシングおよびアンボクシングの変換があります。
  • プリミティブ型の値集合にnullが含まれることはありません。
  • 参照型の値集合はオブジェクトではなくオブジェクトへの 参照 で構成され、常にnullが含まれます。
  • オブジェクトにはオブジェクトアイデンティティがあります。

インラインクラス

準備が整いましたので、言語型システムの中でインラインクラスをどのように収容するかに取り組みましょう。 インラインクラスのモットーは、*「クラスのようにコードを書き、intのように動作する」*です。 このモットーの後半の部分は、インライン型がこれまでに説明したプリミティブ型の実行時の動作と一致しなければならないことを意味しています。 (実際、私たちはプリミティブ型をインライン型の傘下に入れたいと考えています。すでに二分割されている型システムをより多くのカテゴリに分割することは望ましくありません)。

インラインクラスはクラスのようにコードを書くため、クラスが持つことができるほとんどのものを持つことができます:フィールド、メソッド、コンストラクター、スーパーインターフェース、型変数などです。

inline class Point {
    private int x;
    private int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int x() { return x; }
    public int y() { return y; }
}

Valhallaがもたらす最初の大きな違いは、インラインクラスのインスタンス(これを インラインオブジェクト と呼びます)にアイデンティティがないことです。 これは、アイデンティティに依存した特定の操作(同期など)がインラインオブジェクトでは許可されないことを意味します。 混乱を避けるために、従来のクラスを アイデンティティクラス と呼び、それらのインスタンスを アイデンティティオブジェクト と呼びます。

インラインクラスのインスタンスはオブジェクトですが、アイデンティティはありません。

オブジェクトの ID は、とりわけ、 可変性レイアウトのポリモーフィズム を可能にする役割を果たします。 ID を放棄することで、インライン・クラスはこれらを放棄しなければなりません。 したがって、インライン・クラスは暗黙的に final で、そのフィールドは暗黙的に final で、そのスーパータイプには制限があります。 (それらはインターフェイスを実装したり、いくつかの抽象クラスを拡張したりすることができます。) さらに、インラインクラス V の表現は、直接的にも間接的にも、 V 型のフィールドを含んではいけません。 (しかも、インラインクラスは clone() メソッドや finalize() メソッドをオーバーライドすることはできません。)

Valhallaでは、型をプリミティブ型と参照型に分けるのではなく、インライン型と参照型に分けています。 ここでインライン型はプリミティブを包含します。 「参照型」の意味は固定されたままです:参照型はインライン型ではないものです。 これには、宣言された ID クラス、宣言されたインターフェイス、配列型などが含まれます。 型についての図を更新して、インライン型を含むようにしてみましょう。

Types, Valhalla

値集合

値集合がオブジェクトインスタンスへの 参照 で構成されるアイデンティティクラス(または null )とは異なり、インラインクラス型の値集合は、そのクラスの可能な インスタンス の集合です(プリミティブと同様に、インラインクラスは null不可 です)。 インラインオブジェクトは、現在のプリミティブと同様に直接表現されます。このことを値集合の図に反映しましょう。

Values, Valhalla

プリミティブと同様に、インラインクラスの値集合は、オブジェクト参照ではなく、そのクラスのインスタンスの集合です。

すべての型は デフォルト値 を持ちます。 プリミティブ型の場合、デフォルト値はある種のゼロ(0, 0.0, false など)です。 参照型の場合、デフォルト値はnullです。 インラインクラスの場合、デフォルト値はその型のインスタンスのうち、すべてのフィールドがそれぞれの型のデフォルト値を取るものです。 クラス型 C の場合、 C のデフォルト値を C.default と記載するでしょう。 (ジェネリックなコードでは、 T.default と記載するでしょう。型消去によるジェネリクスの場合、 T は常に参照型であるため、これは null と評価されます。 ジェネリクスが特殊化される場合は、T.default も特殊化されます。)

新しいトップインターフェース

コンパイル時と実行時にインラインクラスとアイデンティティクラスを区別するために、 制約付きインターフェースのペアであるIdentityObjectInlineObjectを導入します。 インラインクラスは暗黙的にInlineObjectを実装します。アイデンティティクラスは暗黙的にIdentityObjectを実装します。(両方を実装しようとするとエラーになります。) これにより、アイデンティティに依存する操作を実行する前にオブジェクトのアイデンティティを動的にテストするコードを作成できます。

if (x instanceof IdentityObject)) {
    synchronized(x) { ... }
}

同様に、可変型(およびジェネリック型の境界)での同一性の要求を静的に反映しています。

static void runWithLock(IdentityObject lock, Runnable r) {
    synchronized (lock) {
        r.run();
    }
}

等値性

現在の世界では、プリミティブ型ごとに == が定義されており、参照型では、2つの値が == となるのは、両方がnullであるか、同じオブジェクトへの参照である場合です。 現在、すべての参照はアイデンティティを持つオブジェクトを指しているため、オブジェクトアイデンティティを使用して「同じオブジェクト」を定義できます。

コンポジションを用いて == をインラインオブジェクトに拡張できます。 2つのインラインオブジェクトが == となるのは、2つが同じ型で、それぞれのフィールドについて、全てのペアがフィールドの静的型の == に従って等しい場合です ( float および double を除きます。これらは、 Float::equals および Double::equals のセマンティクスに従って比較されます)。 この定義では、2つのインラインオブジェクトが 置換可能 、すなわち違いが識別できない場合にのみ等値であると言います。

配列

インターフェース I を実装する任意のクラス X (インラインまたはアイデンティティ)の場合、 X の配列に対して次のサブタイプ関係が保持されます。

X[] <: I[] <: Object[]

アイデンティティに依存した操作

現在、いくつかの操作はオブジェクトアイデンティティの観点から定義されています。 これらの一部は、すべてのオブジェクトインスタンスを対象とするように適切に拡張できます。 残りの操作は部分的になります。 これらには以下が含まれます。

  • 等値性。 Object 上で == を全域化します。 つまり、現在において意味がある場合、新しい定義はその意味と一致します。 (次のセクションで説明するように、インラインオブジェクト への参照 に関連する追加の作業がいくつかあります。)
  • System::identityHashCode。 identityHashCode の主な用途は、IdentityHashMapなどのデータ構造の実装です。 等値性を全域化するのと同じ方法で identityHashCode を全域化できます - すべてのフィールドのハッシュからインライン・オブジェクトのハッシュを導出します。
  • 同期。 これは部分的な操作になります。 同期が実行時に失敗することを静的に検出できる場合(インラインクラスでsynchronizedメソッドを宣言するなど)、コンパイルエラーを発行できます。 そうでない場合、インラインインスタンスをロックしようとすると、実行時に IllegalMonitorStateException となります。これが正当化できるのは、対象オブジェクトのロックプロトコルを明確に理解していないのにそれをロックするというのは本質的に不注意によるものだからです。任意の Object またはインターフェースインスタンスをロックするというのは、まさにそういうことなのです。
  • Object::wait および Object::notify。 同期と同じです。
  • 弱い参照。 これについてはどちらにすることもできます。Objectの弱参照を作成することを部分化もできますが、これは厄介な結果になります:弱参照はほとんど役に立たなくなります。なぜなら、何らかの弱いデータ構造を維持したいすべてのクラスは、アイデンティティオブジェクトとインラインオブジェクトの別々のパスに分岐する必要が出てくるためです (これはidentityHashCodeの部分化に似ています)。一方、単純な動作(インラインオブジェクトを保持する弱参照は決して消去されないなど)を選択すると、初めは存在していた、弱参照を使用する目的となるGCフレンドリーな振る舞いの一部が失われます。この件はさらなる分析が必要です。

インライン型と参照型

これまでのところ、「プログラム可能なプリミティブ」に非常によく似たインライン型を構築してきました。 しかし、プリミティブの最大の欠点は、静的にも動的にも、プリミティブとオブジェクトの間ではっきり分かれている点です。 「プログラム可能な」部分は、インライン型がメンバーとスーパータイプを持つことができるという点で、いくつかのギャップを狭めます。 しかし、このギャップをさらに狭くしたいと思います。

現在の世界では、 ボクシング変換を介してプリミティブ型から参照型に変換しています。 これは、より多くの多態的なコードを書くことができるので便利です。 サブタイプまたはボクシングを介して、任意の値を Object で表すことができます。 しかし、ボクシングにはいくつかの重大な欠点があります。 ボックス型は、特注の手作りクラスであり、プリミティブ型への言語的接続は限られています - これは、インラインクラスに確実に対応しません。 さらに悪いことに、結果のボックスには「偶発的な」オブジェクトアイデンティティがあり、多くのVM最適化の妨げになります。 「ボクシングは遅い」という信念は、この偶然のアイデンティティに由来するものです。

私たちがやりたいのは、実行時のアドホックで軽量な方法でインライン型の世界を参照型の世界に接続することです。

ボクシングは死んだ、インライン拡大よ栄えよ

インラインクラスの値集合はオブジェクトインスタンスで構成されますが、アイデンティティクラスの値集合はオブジェクトインスタンス への参照 で構成されます。 この表現の違いは、現在の世界のプリミティブとオブジェクトの違いの主な原因の1つです。

インラインクラスを型システムの残りの部分に接続し、インターフェースを実装して Object を拡張できるようにします。 しかし、インターフェース II を実装するアイデンティティクラス C 、および I を実装するインラインクラス V があるとします。 I の値集合は I ですか? 明らかに、 CV の両方の値集合を含める必要がありますが、これらの集合は構造的にまったく異なります。 一方にはオブジェクトが含まれ、もう一方にはオブジェクトへの参照が含まれます。 これは、橋渡しする必要があるオブジェクトとプリミティブの分断です。 現在の世界は、これを不器用にボクシングで埋めています。 より均一で軽量な方法で橋渡しをしたいと考えています。

インターフェース(および Object )は参照型です。つまり、値集合はオブジェクト参照で構成されている必要があります。 それが、Valhallaの次の大きな違いにつながります。 すなわち、アイデンティティオブジェクトを操作できるのは参照を介してのみですが、インラインオブジェクトでは、直接またはオブジェクト参照を介して、 どちらの方法でも 操作および保存できます。

Valhallaの値の宇宙は、プリミティブ値、インラインオブジェクト、およびアイデンティティオブジェクトとインラインオブジェクトの両方への参照で構成されています。

Valhallaでは、 インライン拡大変換 を介してインラインから参照型に変換します。 これはボクシングと似ていますが、大きな違いがあります: 変換の結果は(ボックスのように)アイデンティティオブジェクトではなく、 インラインオブジェクトへの参照 です。 (結果の ObjectObject::getClass を呼び出すと、ボックス型ではなく、元のインラインオブジェクトのクラスが報告されます。) これにより、VMの最適化能力を損なうことなく、インライン型と参照型の間で必要な相互運用が可能になります。

インライン拡大変換は、ボクシングのパフォーマンスの欠点をほとんど伴わずに、ボクシングの望ましいセマンティクスを提供します。

演算子 ref v は、 v がインラインオブジェクトの場合は v への参照として定義し、 v が既にオブジェクト参照である場合は v 自体として定義すると便利です。 そうすれば、 ref はすべての表現可能な値の全域で定義され、常に参照を返します。 (逆の演算子 unref は部分的であり、インラインオブジェクトへの参照にのみ適用され、2つは 射影と埋め込みのペア を形成します。)

インライン拡大変換は、インライン型からそれが実装する任意のインターフェースおよび Object に存在し、 ref 演算子の適用として定義されています。 これにより、 I の値集合に関する質問に答えることができます。 これには、 C のすべてのインスタンスへの参照と、 V のすべてのインスタンスへの参照が含まれます。

インターフェース(および Object )の値集合は、値 null に加えて、アイデンティティまたはインラインオブジェクトのいずれかであるオブジェクトへの参照で構成されます。 インターフェースまたは Object インスタンスでアイデンティティに依存する操作を実行すると、実行時に失敗する場合があります。

これはボクシングに目新しい名前を付けただけでは?

この時点で、読者は単に用語のトリックを演じただけ、含意のある用語「ボクシング」をまだ含意のない用語「インライン拡大」に置き換えただけではないかと疑問に思うかもしれません。 実施したのが単に名前の変更だけなのであれば、それは確かにトリックになります。 インライン拡大が単に名前変更されたボクシングではない理由は、Valhalla JVMでは、インライン拡大および縮小変換が、対応するボクシングおよびアンボクシング変換よりもはるかに軽いためです。 ボクシングの問題は、アドホック(訳注:プリミティブという限られた型のみに定義されていること)であり、コストがかかることです。 私たちはこれらの両方の懸念に対処しようとしています。

スーパータイプ

インライン・クラスはインタフェースを実装することができます。任意のクラスを拡張することはできませんが、抽象クラスの限られたカテゴリを拡張することができます。つまり、フィールドを持たず、本体が空である引数なしコンストラクタを持ち、それ以外のコンストラクタを持たず、インスタンス初期化子はなく、同期メソッドを持たず、そのスーパークラスがすべて同じ条件を満たしているクラスです(ObjectNumber はそのようなクラスの例です)。

参照射影と値射影

与えられたインラインクラスのオブジェクトへの 参照 の集合に null を加えて記述できると便利なことがよくあります。 インライン型 V が与えられたとき、値の集合が次のように与えられる参照型 R が欲しいと思います。

ValSet(R) = {null} ∪ {ref v : v ∈ ValSet(V)}

このような型 RV参照射影 と呼びます(参照型は、それ自身の参照射影です)。

参照射影は、現在の世界ではラッパークラスが果たす役割を担っています。しかし、すべてのインラインクラスに手書きのアドホックなラッパーを持たせたくはありません。 インラインクラスから参照射影を機械的に導出し、それを参照するための統一された方法を持ちたいのです。 そうすれば、インラインクラスとその参照射影を対応付けたメンタルディクショナリを維持する必要はありません。 任意の型 T に対して、T.refT の参照射影を表します。

インライン・クラスの場合、参照射影と値射影の両方を自動的に作成します。 インラインクラス V の場合、V.refV の参照射影(V のインスタンスへの参照の集合に null を加えたもの)を記述する型であり、V.valV の値射影(V のインスタンスの集合)を参照します。 そして(後で述べるような特別な申し立てがない場合は) VV.val のエイリアスです。 (この参照射影は、V.val のみをサブタイプとして許可するシールされた抽象クラスです。) つまり、抽象クラス C を拡張したインラインクラス V の場合、以下のようになります。

sealed abstract class V.ref
    extends C
    permits V.val { }

inline class V.val extends V.ref { }

そして、VV.val のエイリアスになります。 (この種のエイリアスは新しいものではありません。Stringjava.lang.Stringのエイリアスです。) V.valからV.refへのインライン・ワイドニング変換が自動的に行われます(したがって、V から V.ref への変換も行われます)。 さらに、V.ref から V.val へ(したがって、V.refからVへ)のインラインナローイング変換を定義します。 それは unref演算子を適用するもので、nullに対してはNullPointerExceptionをスローします。

この一対の変換によって、インラインクラスは、歴史的にラッパー型とプリミティブがそうであったように、参照射影と同じ関係を持ちます。 ボクシング変換(オートボクシング、条件式の型付け、オーバーロードの選択)の観点から定義されている既存のルールは、インラインの拡大と縮小の変換を組み込むために、簡単に拡張することができます。 その結果、既存の言語ルールと、これらの変換に関するユーザーの直感は、新しい世界でも変更されることなく前進することができます - しかし、ボックス化の実行時間コストはありません。 なぜなら、現在のボクシングのように、インラインの拡大は偶然のアイデンティティを持つ新しいオブジェクトの生成を強制するものではないからです。

参照型 R はすでにそれ自身の参照射影であるためのすべての要件を満たしているので、 参照型 R については、 R.refR 自身のエイリアスにします。 これで、すべての型 TT.ref と呼ばれる参照射影を持つことが保証されました。

クラスミラー

インラインクラス V は、2つの型(V.refV.val 、それに型エイリアス V)を生成します。 そして同じく、2つのクラスミラーも生成されます。 しかし、参照射影は抽象クラスなので、どのインスタンスもそれが参照射影のインスタンスであることを報告することはありません。 V の値への非null参照は、V.valのインスタンスであることを報告します。

インターフェース

歴史的には、クラスがインターフェースを実装することはいくつかのことを意味していました。

  • 適合性。 クラスには、メンバーとして、インターフェースのすべてのメンバーがあります。
  • 推移性。 このクラスのサブクラスもインターフェースを実装します。
  • サブタイピング。 クラス型は、インターフェース型のサブタイプです。

インラインクラスをサポートするために、この最後の箇条書きであるサブタイピングを少しだけ洗練させる必要があります。 インライン・クラスの 参照射影 は、インターフェース・タイプのサブタイプであると言います。 (アイデンティティクラス型はそれ自身の参照射影なので、この宣言はすべてのクラスに適用されます。) 同様に、インラインクラスが抽象クラスを拡張している場合、参照射影は抽象クラスのサブタイプであることを意味します。

オブジェクト

Object には、すべてのクラス、インライン、およびアイデンティティのルート型としての役割があるため、インターフェースと多くの特性を共有しています。 既に述べたように、すべてのインライン型から Object へのインライン拡大変換があります。

ただし、 Object は具象クラスであるため、残念ながら、コンストラクターを介して Object 直接インスタンス化することは可能です。 インターフェースは継承されるため、 ObjectInlineObjectIdentityObject も実装できません。 ですが、 new Object() によるインスタンス化の結果はアイデンティティ型のインスタンスである必要があります(他の理由で Object をインスタンス化するポイントがないため)。

この罠からはいずり出るには、手の込んだ動きが必要です。 まずは IdentityObject を返す静的ファクトリー Object::newIdentity を作成し、 それから、さまざまなツール(コンパイラー警告やJITのマジックなど)を使って、既存のソースおよびバイナリーでの new Object() の使用をそちらに移行しようとすることから始めましょう。 そして最終的には、直接インスタンス化するための Object コンストラクターを( protected にしてしまうことで)「廃止」するのです。

等値性、ふたたび

インラインオブジェクトの == 拡張は完了していません。 インラインオブジェクト自体に対しては定義しましたが、参照型で、値集合の中にインラインオブジェクトへの参照を持つ可能性のあるものについてはまだ定義していません。 これについて、2つのオブジェクト参照が等しいとは、両方ともnullであるか、同じアイデンティティオブジェクトへの参照であるか、または == が成立する2つのインラインオブジェクトへの参照である場合とします。 これにより、 ==代入可能性 のセマンティクスがすべての値に拡張されます。 すなわち、2つの値は、 NaN が持つ従来の動作を除いて、どのような方法でも区別できない場合にのみ == です。

これにより、 == について、次の有用な不変式( NaN の従来の動作を除くすべてで成り立つ)が得られます。

  • == は反射的、すなわちすべての v に対して v == v です。
  • 2つのインライン値が参照に拡大されるとき、元の値が == だった場合にのみ、結果も == となります。
  • 2つの参照がインラインオブジェクトに縮小されるとき、元の参照が == だった場合にのみ、結果も == となります。

Object::equals の基本実装は、 == に委任します。 Object::equals を明示的にオーバーライドしないインラインクラスでは、それがデフォルトです。 (同様に、 Object::hashCode の基本実装は System::identityHashCode に委任します。これもデフォルトです。)

参照射影が必要な理由

参照射影の定義は意味があり、参照型との関係についての既存の直観と一致していますが、これらの型が非常に重要である 理由 をまだ十分に動機付けていません。

インライン型を使用できない場合は次のようにいくつかあります。

  • 無効値。 Nullはインライン型の値ではなく、すべての参照型の値ですが、「V型のnull値を許可する値」という概念を表現したい場合もあります。
  • 非平坦化。 インライン値はオブジェクトと配列に規則正しくフラット化されます。 通常、これは私たちが望むものです。 ただし、場合によっては、メモリー使用率をより細かく制御する必要がある場合があります。たとえば、「幅の広い」インラインクラス(多くのフィールドを持つクラス)があり、それらの疎な配列が必要な場合などです - 平坦化された値の配列よりも、参照の配列の方がメモリーを効率的に使える場合があります。
  • 再帰表現。 インラインクラスは、表現について自分自身に再帰的に依存することはできません。 通常、これは重大な制限ではありませんが、参照型でこれをシミュレートしたい場合もあります(たとえば、「次の」フィールドを持つ Node クラス)。
  • 型消去によるジェネリクス。 既存の型消去ジェネリクスは、型パラメーターが参照型であることを前提としています - アイデンティティ依存の操作や、null値などが許可されます。特殊化されたジェネリクスが使えるようになって、インライン型と型消去ジェネリクスの相互運用ができるようになるまで、型パラメーターには参照型を使用したいのです。
  • 妥当なゼロがない場合。 インライン型のデフォルト値は、すべてのフィールドがデフォルト値をとる値です。 一部の型では、この値は妥当です( Point のデフォルト値として (0, 0) は妥当です)が、場合によっては( Rational など)、この値は無意味か、または危険ですらあります。 そういった場合は、初期化されていない値を表すために null を使用できるように、参照型を使用したいこともあります。

これらすべての状況で、インライン型の代わりに参照型を使用できます。 ただし、 Object などの広い参照型を使用すると、多くの損失が発生します - 型安全性が失われ、クライアントはインラインの世界に戻るためにコードに未チェックのキャストを挿入する必要があります。 インライン型に参照射影を使用すれば、インラインの拡大と縮小の変換によって、値集合が特定のインラインクラスの値集合と密接に結び付けられた参照型があり、明示的な変換を使用せずにそのインラインクラスに自由に変換できます – これにより、私たちが望む型の安全性と利便性が救われます。

例として、特殊なインスタンス化をサポートするために Map インターフェースを移行する問題を考えてください。 Map::get メソッドは、要求されたキーがマップに存在しない場合は null を返しますが、 MapV パラメーターがインライン型の場合、 null は値集合のメンバーではありません。 get() メソッドを以下のように宣言すれば、これを捕捉できます。

public V.ref get(K key);

これは、 Map::get の戻り値の型が V への参照か、または null 参照のどちらかになるという概念を捉えています。

移行

これまでに説明した手法は新しいコードには十分ですが、準備しておきたい移行シナリオがいくつかあります。

移行: 値ベースのクラスからインラインクラスへ

値ベースのクラスがすでにいくつも存在していて、それらはインラインクラスへのスムーズな移行を可能にするために設計された一連の制限を満たしています。 そのような例の1つに java.util.Optional があります。これは、Java 8にインラインクラスがあったとしたならインラインクラスとして宣言できたはずです。 Optional をインラインクラスに移行して、インラインクラスの実行時の利点を活用したいと思います。 ( java.util.time パッケージのクラスも同様の状況にあります。)

既存のクライアントには、変数の型、メソッドのパラメーターまたは戻り値の型、型パラメーターなどで、 Optional 型が多数使用されています。 これらのクライアントはすべて、 Optional が参照型であると想定しており、参照型はnull可能です。 したがって、 Optional を直接インライン型に移行することはできません。

次善策は、 Optional を何らかのインライン・クラスの参照射影として定義することです。 これは、Optional を抽象クラスに移行し、ただ一つのインラインクラス(値射影 Optional.val)だけ許可するようにシールされるように手配することを意味します。

この移行後、 Optionalインスタンス はアイデンティティクラスではなくインラインクラスになりますが、既存のコードは引き続き、参照を介して Optional 値を格納したり渡したりします。 それらを直接表現することが実際に違いを生むのは、それらがヒープに出会う場所、つまりフィールドと配列要素です。 そしてそこでは、 Optional のそれら特定の用途を Optional.val に自由にかつ段階的に移行できます。 インラインの拡大と縮小の変換によって、フィールドまたは配列を Optional から Optional.val に変更するのは、ソース互換の変更になります。 既存のAPIは、参照射影である Optional を引き続き使用する可能性が高いでしょう。

このトリックを達成するために、Optional のインライン・クラスを宣言して参照射影 Optional.ref と値射影 Optional.val を生成したいのですが、エイリアス Optional を逆にして値射影ではなく参照射影を参照するようにしたいのです。 これは、宣言を修正して ref-defaultインライン・クラス であることを示すことで実現できます。

ref-default inline class Optional<T> {
    // current implementation of Optional
}

ref-default修飾子が持つ唯一の効果は、装飾されていない型名が指し示すのは、2つの射影のうちどちらかを決定することです。

このような移行から生じる可能性のある非互換モードが1つあります - クライアントが getClass() の結果と Optioinal.class を比較する場合です。 移行前は、 Optional インスタンスは Optional.class クラスを持っています。 Optional が抽象クラスに移行されると、 Optional インスタンスは 自分自身が値射影 Optional.val のインスタンスであると報告するでしょう。

M.class の使用を取り巻くこの非互換性は、ここで概説する移行アプローチの主要な互換性コストです。これは、 getClass() の結果とインターフェースや抽象クラスのクラスリテラルとを == で比較するときにコンパイル警告を発行することで多少緩和できます。実行時に失敗することがわかっているためです。

移行: プリミティブ

インライン型にプリミティブの抽象化という役割を割り当てましたが、実際にプリミティブをインライン型に包含できるようにするために、さらに作業が必要です。

ラッパー型である Integer とその仲間たちを、Optional の移行と同じ手法を用いて、シールされた抽象クラスに移行することから始めます。 これらのインターフェイスは、プリミティブの参照射影になります。 これを行うには、まず、プリミティブラッパーのアイデンティティへの依存性をユーザーから解放しなければなりません。 これを行うには、パブリックなコンストラクタを非推奨にして(実際には削除するのではなくプライベートになります)、移行期間中はInteger のインスタンスを「永久にロックされた」状態にします(ここでのアプローチについては JEP 169 を参照してください)。

この動きは、プリミティブラッパーのアイデンティティに依存するコードを壊すリスクがあります。代入可能性を持った == の定義を考えれば、 == による比較はほぼ問題になりませんが、ラッパーをロックするコードは実行時に失敗するでしょう。

その後、プリミティブ型をインラインクラスとして明示的に宣言できます。

inline class int { /* implementation */ }

自己参照については明らかに多少の調整を行う必要がありますし、レガシーラッパーの名前をプリミティブに合わせるために、さらにいくつかの修正を加えましたが、今や、 int にスーパーインターフェースとインスタンスメソッドを追加するのも自由ですし、通常はクラスのように扱うことができます。 参照射影 int.refInteger のエイリアスになっていて、 Integer.valint のエイリアスになっています。 2つの型の関係は、移行された値ベースのクラスの場合と同じです。 プリミティブが真のインライン型である場合でも、配列の間には引き続き以下のサブタイピング関係があります。

int[] <: Integer[] <: Object[]

次に、intInteger の間で定義された2組の変換を調整しなければなりません。 既存のボクシング変換と、新たなインライン縮小・拡大変換です。 しかし、便利なことに、ラッパークラスの偶発的なアイデンティティ(非推奨です)を除き、これらは同じセマンティクスを持つように定義しています。 ボクシングは、ランタイムが規則正しい最適化によって除去することがより単純になりますが、その一方で、変換や、オーバーロード選択や、推論にまつわるすべての既存ルールは維持されます。 同様に、オーバーロード選択でのボクシングの役割を一般化して、インラインの拡大と縮小を代わりに使用できるため、この移行ではオーバーロード選択の決定方法は変更されません。

移行: 特殊化されたジェネリクス

ジェネリクスは現在、単一の参照に型消去されます。 いずれはジェネリクスがその表現を特殊化できるようにして、 ArrayList<Point> のようなジェネリック型も Point[] がもたらす平坦化と密度の利点を得ることができるようにしたいと思っています。 これには追加の時間がかかり、移行互換性の問題がさらに発生します。 ArrayList を特殊化可能な型に移行しても、既存のクラスファイルは 型消去による ArrayList への参照が多く残されたままでしょうから、これらは引き続き動作する必要があります。

将来の機能として、特殊化されたジェネリクスの余地を残そうとしています。 現在の型消去されたジェネリクスは、参照型でのみ機能し続ける可能性があります。 List<int>Optional<Point> などの特殊化された型については、将来的に自然な表記法を予約したいと考えています。 これを行うには、型消去されたジェネリクスでは、参照型のみが型引数として有効であることを要求します。 今日、人々は List<Integer> と書いています。 これは List<int.ref> および List<Point.ref> と一般化されます。

まとめ

私たちが取ったアプローチは、プリミティブと参照における既存の分断構造を維持する一方で、より規則的でパフォーマンスの高いものにすることです。 ボクシングはずっとコストが低くなって影の中に遠ざかり、「インライン」は「プリミティブ」の新しい言葉であり、プリミティブの集合は拡張可能です。 私たちは「重いボクシングを伴う参照とプリミティブ」から「軽いボクシングを伴う参照とインライン」に移行しましたが、2つの世界はほぼ同型のままです。

現在の世界 Valhalla
参照とプリミティブ 参照とインライン(プリミティブはインライン)
プリミティブにはボックスがあります インラインには参照射影があります
ボックスにはアイデンティティがあり、 getClass() で表示されます 参照射影は実行時に表示されません
ボクシングとアンボクシングの変換 インラインの縮小拡大変換、ただしルールは同じ
プリミティブは組み込みで、魔法 プリミティブはほとんど単なるインラインクラスで、追加のVMサポートを備えています
プリミティブにはメソッドもスーパータイプもありません プリミティブはインラインクラスであり、インラインクラスにはメソッドとスーパータイプがあります
プリミティブ配列は単相です インライン配列は多相です

このドキュメントでは、インライン・クラスの Java 仮想マシン・ビューについて説明します。 これは必ずしもJava言語ビューと同じではないことに注意してください。 読者の方は、このドキュメントから Java 言語についての結論を導き出す際には注意が必要です。

インラインクラス

Valhalla 以前は、すべてのオブジェクト(クラスや配列のインスタンス)は一意のオブジェクト ID を持っていました。 Valhalla では、クラスに ACC_INLINE フラグを付けることで、そのインスタンスが ID を持つかどうか(「identity クラス」)、あるいは ID を持たないかどうか(「インラインクラス」)を選択できるようになりました。

抽象仮想マシン(クラスファイルで記述されている)では、インライン・オブジェクトとアイデンティティ・オブジェクトの両方が、オブジェクト参照 を通じて排他的に参照されます。 しかし、JVMはインラインオブジェクトがアイデンティティを持たないことを知っているため、インラインオブジェクトのレイアウト(平坦化)、インスタンス化、メンバーアクセス、および呼び出し規約(スカラー化)を日常的に最適化することができます。

キャリアと基本型

JVM型記述子(JVM仕様 4.3.2)では、基本型 を表すために先頭の文字を使用します。 現在、8つの基本型(IJなど)に対応する8つのプリミティブ型と、オブジェクト参照(L)に対応する1つの基本型があります。 (Vという文字は、型記述子には現れますが、型とはみなされません。)

これら9つの基本型は5つの キャリア型intlongfloatdouble、オブジェクト参照)に対応しており、 スタックスロットやローカル変数スロットでの値の異なる表現に対応しています。 (これら2つの集合の違いは、byteshortcharbooleanintに消去してIキャリアを使用していることに由来します。) プリミティブキャリアは、スタックまたはローカル変数スロットに直接値を格納し(floatdouble 値は隣接する 2 つのスロットを使用します)、 オブジェクト参照(L)キャリアは、対応するスロットに オブジェクト参照 を格納します。

インライン型を記述するために、インラインオブジェクトへの参照を表す新しい基本型 Q を追加します。 Q ディスクリプタは、L ディスクリプタと同じ構文構造(例えば、Qjava/lang/int;)を持っています。

L ディスクリプタの構文形式を再利用することに加えて、Q ディスクリプタは L キャリアも再利用します。 JVMでは、Q ディスクリプタの下の参照がnullにできないという事実以外に、 インラインオブジェクトへの参照とIDオブジェクトへの参照の間には構造的な違いはありません。

L ディスクリプタと Q ディスクリプタの選択は、名前付きクラスが ID クラスに解決するかインラインクラスに解決するかに密接に結びついています。 L ディスクリプタがインライン・クラスに解決したり、Q ディスクリプタが ID クラスに解決したりするのは、リンケージ・エラーです。 (別個の基本型デシグネータの必要性は、フィールド・レイアウト時など、インライン・クラスは ID クラスよりも積極的にプリロードする必要があることに起因しています。)

スーパータイプ

インライン・クラスはインターフェイスを実装することができ、特別なクラスであるObjectと同様に、ある制限された抽象クラスを拡張することができます。 (抽象クラスの制限には、フィールドを持たないことや空のコンストラクタが含まれます。) つまり、インターフェース型、適切な抽象クラス型、またはObjectの変数は、IDオブジェクトまたはインラインオブジェクトのいずれかへの参照である可能性があります。 JVMは、このような拡張を通常のサブタイプとして扱いますので、インライン・オブジェクトへの参照は、キャストなしでObject、または適切なインターフェイスまたは抽象クラス・タイプへの参照に拡張することができます。 インラインクラスはコンストラクタを持ちません。 代わりに、静的なファクトリメソッド(名前は<new>)を持っています。

制約

インライン・クラスには、対応するIDクラスと比較していくつかの制限があります。 インライン・クラスのインスタンス・フィールドには ACC_FINAL とマークする必要があり、InlineObject インタフェースを実装する必要があります。(直接または間接的に IdentityObject を実装することはできません。) Object 以外のクラスを拡張する場合、そのクラスは抽象化されていて、フィールドを持たず、空の(ACC_ABSTRACT とマークされている可能性がある)無引数コンストラクタを持つ必要があります。 (インラインクラスの静的ファクトリは、スーパークラスのコンストラクタを呼び出しません。なぜなら、定義上は空でなければならないからです。)

インラインクラス V のフィールドは、直接または間接的に V 型のフィールドを持つことはできません。

バイトコード

インラインクラスの組み込みは、多くのバイトコードに影響を与えます。 いくつかは新しいものですが、インライン型に適用されたときに追加の動作や制限を得るものもあります。

既存の a* バイトコードが拡張され、identityクラスとインラインクラスの両方への参照を一様にサポートするようになりました。

インラインクラスのインスタンスを構築するために使用される2つの新しいバイトコードがあります。

  • defaultvalue はインラインクラスインスタンスの new のアナログです。
    これは、スタック上にインラインクラスのデフォルト (すべてゼロ) インスタンスへの参照を残します。
    (初期化されていないフィールドや配列要素も、あたかも defaultvalue によるものであるかのように、その型のデフォルト値に初期化されます。)
  • withfield はインラインクラスのインスタンスのための putfield のアナログです。
    インラインオブジェクトへの参照とスタック上の新しいフィールド値を受け取り、指定されたフィールド以外はすべて元のインスタンスと同じフィールドであるインラインクラスのインスタンスへの参照をスタック上に残します。

withfield バイトコードは制限されています。 このバイトコードは、変更されるインラインクラスを宣言しているクラスのみが実行できます。 これは新しい値の生成をカプセル化しているので、クラスは実装をエスケープした値がその不変量に従っていると見られることを保証できます。 new バイトコードと putfield バイトコードはインライン・クラスのインスタンスでは使用できず、withfield バイトコードは ID クラスでは使用できません。

aastore 命令は、インライン・クラス・インスタンスの配列に値を格納する前に、要素の値に対してヌルチェックを行います。

acmp* 命令は、そのオペランド間でより洗練された比較を実行します。 2つのオブジェクト参照は、両方ともnullであるか、両方とも同じidentityオブジェクトへの参照であるか、両方とも同じ型のインラインオブジェクトへの参照であり、すべてのフィールドが "等価"である場合、等値とみなされます。 等価とは、適切な等値比較をフィールドに再帰的に適用することを意味し、プリミティブには通常の等値 (Float::equalsDouble::equalsのセマンティクスで比較されるfloatdoubleフィールドを除く)、参照にはacmpを使用します。 インラインオブジェクトへの参照に適用された場合、if_acmpnull 命令は常に false を返します。

Q-World から L-World へ

Valhalla プロトタイプの以前のイテレーションでは、Q ディスクリプタは別のキャリアを持っていましたが、それは L キャリアとの相互運用性がありませんでした。(プリミティブが明示的に変換しないと参照型との相互運用性がないのと同じように。) また、プリミティブと同様に、Q 型にはスーパータイプがありませんでした。インターフェイスとオブジェクトのメンバへのアクセスは、L 型のコンパニオンクラスへの変換を経なければなりませんでした。(ボックスとして機能しますが、現在のボックスのような偶然のアイデンティティはありません。)

同様に、インライン クラスは以前は独自のデータ移動バイトコード (v*) を持っていましたが、L-World では、identity とインライン オブジェクトの両方へのオブジェクト参照は一様に a* バイトコードを介して移動します。 最後に、Q-world では、インラインオブジェクトの配列は Object の配列と共変性を持っていませんでした。

この区別は、インラインクラスを「強化されたプリミティブ」として扱いました。 クラスのようにコードを書くことはできますが、良い意味でも悪い意味でも int のように振る舞います。 このため、既存のアイデンティティ・クラスをインライン・クラスに移行したり、アイデンティティ・オブジェクトとインライン・オブジェクトを統一したりすることが非常に困難になりました。 それは、すべてのレベル(記述子、バイトコード、サブタイプ関係)でインラインクラスとアイデンティティクラスの間に継ぎ目があったからです。 L Worldのデザインは、このアプローチが生み出した課題に大きく影響されています。

このドキュメントでは、インライン・クラスおよびインライン型に対する操作が、Java ソース・コードから Java クラスファイルにどのように変換されるかについて説明します。

クラス宣言

インラインクラス宣言は、スーパークラス(Objectまたは適切な抽象クラス)とスーパーインターフェースを持つことができます。

class C extends A implements I { }

コンパイル時には、ソースレベルの型C.refC.valに対応するC$refC$valという2つのクラスファイルが生成され、以下のようになります。

[ PermittedSubtypes(C$val.class) ]
[ NestMembers(C$val.class) ]
abstract sealed class C$ref
    extends A implements I
    permits C$val {
}

[ NestHost(C$ref.class) ]
ACC_INLINE class C$val extends C$ref {
}

さらに、これによりC型が生成され、これは上記の型のいずれかのエイリアスとなります(Cref-defaultval-defaultかによって異なります)。

ここでシーリングを使用するということは、クラスC$refの値がC$valの値への参照であるか、null参照になることを意味します (事実上、C$refC$valと区別された値nullとの接続になります)。 さらに、生成された2つのクラスはネストメイトであり、お互いのプライベート・メンバにアクセスすることができます。

メンバー

Cで宣言されたメンバは、C.refC.valの両方のメンバになります。 クラス宣言のメンバを射影クラスファイルのメンバに変換する方法はいくつか考えられます。 私たちは、VM がすでに行っていることを利用して、重複や、ブリッジ・メソッドなどの翻訳アーティファクトの必要性といったものを最小限に抑え、将来の移行時の互換性の問題を最小限に抑えるアプローチを選択します。 このアプローチでは、状態関連のメンバ(フィールドとコンストラクタ)を値射影に、動作関連のメンバ(メソッド)を参照射影にソートします。 フィールドは暗黙のうちに最終的なものとなり、ソース・レベルのコンストラクタはクラス・ファイル内のファクトリー・メソッドにマッピングされます。

inline class C {
    private int x;

    public C(int x) { this.x = x; }

    public int x() { return x; }
}

これを翻訳すると、以下のようにメンバーがソートされ、それを考慮してメソッド本体の翻訳に様々な調整が加えられています。

abstract sealed class C$ref permits C$val {
    public int x() { return ((C$val) this).x; }
}

ACC_INLINE class C$val extends C$ref {
    private final int x;

    public static void <new>(int x) {
        defaultvalue C$val
        iload_0
        withfield C$val.x:int
        areturn
    }
}

静的なメンバは参照射影に変換されます。

型の利用とメンバーアクセス

フィールド記述子やメソッド記述子など、ほとんどの場合、C.refの使用はLC$ref;C.valの使用はQC$val;と訳され、Cの使用は、Cがref-defaultかval-defaultのインラインクラスとして宣言されているかどうかによって、これらのいずれかに訳されます。

C$valのすべてのインスタンスメンバへのアクセス、C$ref上のメソッドアクセスは通常通りに翻訳されます(getfieldinvokevirtualなど)。 C$refからのフィールドアクセスは、最初にC$valにキャストし(この2つは同じアクセシビリティを持ちます)、値射影を通してフィールドにアクセスすることで変換されます。 C$ref のインスタンス化は、C$val のインスタンス化に続いて C$ref へのキャストが行われると変換されます。 C$valを介した静的メンバへのアクセスは、C$refへの展開とそこにあるメンバへのアクセスによって変換されます(これはプライベートな静的フィールドも扱います)。 プライベートメソッドの呼び出しについても同じことが言えます(継承されないので)。 これらの変換結果はユーザには透過的です。ユーザは両方のプロジェクションを、すべてのメンバがCで宣言されていると見なします。

この翻訳スキームは2つの目標を達成するために選択されました: クラスファイル内のメンバの重複を最小限に抑えること(抽象メソッドやブリッジメソッドなど)と、(修飾されない名前の意味を除いて)val-default vs ref-defaultや、新たなバイナリ互換性の制約を生み出す可能性のある他の特性に翻訳の決定を条件付けしないことです。

変換とサブタイピング

値射影と参照射影の関係は、言語モデルとVMモデルへの変換では異なります。 言語モデルでは、値射影は参照射影のサブタイプではありません。その代わり、両者はインラインの縮小変換と拡大変換によって関係していますが、VMでは、実際のサブタイプ化によって関係しています。

VMと言語とで違うモデルをわざと選んでいるのはおかしいと思われるかもしれません。 が、見ての通り、この選択は言語の制約を言語(制約の属する場所)に残します。 これにより、VM は主に効率的な翻訳ターゲットとしての役割に集中することができます。

インラインの縮小と拡大の変換は、意味的には、よりよく知られているアンボクシングとボクシングの変換に似ていますが、ボクシングに関連する負のパフォーマンスコストの多く(インダイレクト、割り当て、偶発的な同一性)を共有していません。 C$valC$refに変換する場合、これは言語レベルでは変換とみなされますが、VMはすでにC$valC$refのサブタイプとして認識しているため、この変換のために実際のバイトコードを発行する必要はありません。 逆に、C$refC$valに絞り込むときに、言語コンパイラはチェックキャストバイトコードを出力しますが、VMはC$refC$valだけを許可するようにシールされていることを知っているので、これはヌルチェックに下げることができます(または最適化によって完全に削除されます)。 そのため、言語レベルではボクシングとアンボクシングのように見えていたものが、VM内では「何もしない」および「ほぼ何もしないに等しい」状態になってしまうのです。

それでも、なぜC$valC$refの間のサブタイプを単純に言語レベルで公開することを選ばなかったのでしょうか? それは、言語レベルでプリミティブとインラインを統一する道筋を残したかったからです。 現在のところ、この言語では、アドホックなボックス化とアンボックス化の変換によってintIntegerが関連しています。 言語モデルで概説したように、プリミティブを通常のインラインクラスに移行し、レガシーなボックス型がそれらのクラスの参照射影となるようにしたいと考えています。 プリミティブに2つの異なる種類の「箱」(レガシーな重い箱と新しい軽い箱で、前者はボクシングを介して、後者はサブタイプを介して関連しています)を持たせることは、永久的な欠陥になります。 そして,新しい第2の変換セット(サブタイプ)を持つことは,既存のオーバーロード選択の決定を変更することと相容れないかもしれません。 この欠陥を取り除くために、intInteger間の変換のセマンティクスを保持し、それをすべてのインラインの拡大/縮小変換に拡張します。 これは,レガシーなボクシング変換が通常のインラインの拡大・縮小変換であるようにするためのものです。

さらに、ボクシングの関係は、型推論やオーバーロード選択にも役割を果たしています。 そして、インラインでの拡大縮小の意味合いがボクシングのそれとマッチするように選択することで、 これらの複雑で広汎な言語機能は、インラインクラスに拡張しても、プリミティブを特別に扱うことなく、ユーザーが期待する通りに動作し続けることができます。

これは、"ボクシング" の名前を "インライン拡大変換" に変更しただけで、修辞的なトリックを使っているように見えるかもしれません。 しかし、違いは新しい翻訳対象ができたということです。 これは、インラインクラスとその参照射影のレイアウト、インスタンス化、呼び出し規約を日常的に最適化できるものです。 効率的な翻訳ターゲットに使い慣れた言語のセマンティクスを移植しただけで、(パフォーマンス以外は)何も変わっていないかのような錯覚に陥っています。

プリミティブ

言語モデルで議論されているように、この話の重要な目標は、プリミティブの性能特性を保持しつつ、プリミティブが「ほとんどただの」インラインクラスになるようにプリミティブを移行させることです。 現在のプリミティブと参照型の間の区分けは、インライン・クラスとアイデンティティ・クラスの間の区分けに移行し、各ドメインのルール(およびそれらの間の関係)は、現在の区分けの対応する側のルールとほぼ同じになります。 この移行は、おそらく段階的に行われるでしょう。

最初のステップは、言語がプリミティブを、暗黙のうちに宣言されたインラインクラス(例えば、java.lang.int)の値射影であるかのように扱い、そのラッパークラスを、そのインラインクラスの参照射影であるかのように扱うことです。 (そうすると、int.refIntegerのエイリアスになり、Integer.valintのエイリアスになります。) これにより、型推論、オーバーロード選択、型結合適合性、オートボックシングなどのルールが、現在のプリミティブの扱い方と完全に一致した方法で、プリミティブとインラインを統一的に扱うことができるようになります。

後になって、実際にintjava.langで(特別な)クラスとして宣言し、インターフェイスとメソッドを持つようにして、ジェネリクスの話が完成したら、最終的にintComparable<int>を実装することができるようになるかもしれません。

もちろん、JVM は、プリミティブの直接サポート(例えば、I キャリアや i* 命令)を継続します。 言語コンパイラには一定の自由度があるでしょう。 それは、有益な場合や移行互換性のために必要な場合(フィールドやメソッド記述子など)は 生成されたコードの中でI キャリアとi*命令を使用し、他の場所では Qjava/lang/int を使用するといったものです。

このスキームには、動作的に互換性のない変更が1つ含まれています。 それは、プリミティブなラッパークラスの一つのインスタンス上での同期化は IllegalMonitorStateException をスローします。 これを軽視しているわけではありませんが、ほとんどの場合、これを行うことはエラーであり、これが完全なエラーではないごく少数のケースでは、簡単な回避策が存在します。

リフレクション

Q-Worldでは、参照射影はインラインクラスファイルからJVMによって機械的に導出されていたため、JVMは参照射影用の合成ミラーも作成しなければなりませんでした。 現在では、値射影と参照射影が実クラスファイルであるため、実クラスミラーを使用してリフレクション作業のほとんどを行うことができます。

インラインクラスに関するより多くの情報(このクラスはインラインクラスなのか、参照射影は何かなど)を提供するためにリフレクション APIを拡張したいと思うかもしれません。 また、ミラーの一方または両方のリフレクション動作を微調整して、より言語に近い動作をするようにしたい場合もあるでしょう -- あるいは、そうではなく、単に翻訳モデルを公開するだけのリフレクションにしたい場合もあるでしょう。

@matarillo
Copy link
Author

A First Look at Java Inline Classes https://www.infoq.com/articles/inline-classes-java/

@matarillo
Copy link
Author

matarillo commented Apr 11, 2020

defaultvalue and withfield implementation in openj9

The initial implementation should operate in the same way that the new bytecode does -- the instance should be allocated on the heap and a reference should be placed on the stack. A later version could consider value type buffering, where new instances are instead allocated in a valuebuffer pointed to from the Java stack, and which disappears when the stack frame is popped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment