[Java16]値ベースのクラスに対する警告

# この記事について

2020/3/16にリリースされたJava16の言語仕様に関わる変更について，自分の理解を深めるのを兼ねてまとめてみます．プレビュー中の機能は含みません．

+ [record](https://qiita.com/ReiTsukikazu/items/6dc3ec9ea9646c472db0)
+ [instanceof パターンマッチング](https://qiita.com/ReiTsukikazu/items/be973af7070c92bf87d5)
  + [型指定の制約について](https://qiita.com/ReiTsukikazu/items/bfac1d3bbac60915b73e)
+ 値ベースのクラスに対する警告(この記事)

値ベースのクラスに関しては，Java16で直ちにコンパイル結果が変わったりするわけではありませんが，今後のバージョンで互換性を破壊する変更が入りそうなので，早めに警告をだすことにしたようです．

## 出典

+ [JEP 390: Warnings for Value-Based Classes](https://openjdk.java.net/jeps/390)

# 概要

+ Project Valhalla の準備である
+ プリミティブ型のラッパークラスのコンストラクタを使うコードに警告を出す
+ 標準ライブラリの値ベースのクラスを`synchronized`で使っているコードに警告を出す

## Project Valhalla

従来のJavaのデータは，プリミティブ型と参照型の2つにはっきりと分かれています．このようなメモリ上のデータ表現を拡張するために検討・準備を行っているのが，[Valhalla Project](https://openjdk.java.net/projects/valhalla/)です．主に，

+ 値型([JEP 169](https://openjdk.java.net/jeps/169)): プリミティブ型のように使えるユーザー定義型を実現する(メモリ配置の効率化など)
+ 総称型の特殊化([JEP 218](https://openjdk.java.net/jeps/218)): 型変数にプリミティブ型を使えるようにする(`List<int>`など？)

という内容が検討されているようです．

Java16では前者の値型に関係する変更があります．

## プリミティブクラス

前述の値型のことだと思われます[^termPrimitiveClass]．特徴として，

[^termPrimitiveClass]: 文献によって異なる用語が使われています．まだ検討段階の要素なので，用語も安定していないのでしょうか…

+ 「"identity-free"であるように宣言できる」
    + 参照アドレスに依存せず，内容のみが意味を持つ
    + プリミティブクラスの値どうしを比較するときは，アドレスの比較ではなく内容の比較を行う
        + コード上は`==`と書いてあっても，実際には`equals`に相当する比較が行われるようになる見込み
+ 「インライン，あるいは平坦化された表現を持てる」
    + 例えば配列であれば，フィールドの値だけを並べるメモリ配置になったりするのでしょうか？

この結果，

+ 「インスタンスをメモリ間で自由にコピーできる」
+ 「インスタンスのフィールドの値のみを使ってエンコードできる」

ようになり，コードが分かりやすくなることや，パフォーマンスの改善が期待されます．


## 値ベースのクラス

「値ベースのクラス([value-based class](https://docs.oracle.com/en/java/javase/16/docs/api/java.base/java/lang/doc-files/ValueBased.html))」とは，[以前から](https://docs.oracle.com/javase/jp/15/docs/api/java.base/java/lang/doc-files/ValueBased.html)存在したクラス設計の考え方です．クラスが値ベースである要件は，

+ インスタンスフィールドが全て `final` である(内容は可変オブジェクトへの参照であってもよい)
+ `equals`, `hashCode`, `toString` がインスタンスフィールドの値のみから計算されるように実装されている
    + ID(アドレス)にはよらない
+ 全てのメソッドは，equalなインスタンスを完全に置き換え可能なように扱う
    + 2つのインスタンス`x`, `y` が`equals`により等価と判定されるならば，`x`と`y`を入れ替えても結果が変わらない
+ インスタンスを同期モニターに使用しない
+ 外部からアクセス可能なコンストラクタを持たない
    + 経過措置として，`@Deprecated`指定のコンストラクタは許容
+ 「呼び出し毎に一意のIDのインスタンスを返す」ことが保証されたファクトリメソッドを<b>持たない</b>
    + 別のファクトリメソッド呼び出しで得られたインスタンスであっても，`equals()`によって等しいならば`==`によっても等しい可能性を排除できない
+ クラスが`final`である
+ `Object`か抽象クラスを直接継承する
+ 祖先の抽象クラスはすべて，インスタンスフィールド・インスタンス初期化子を持たず，コンストラクタが空である

以上を満たすことです[^ValueBasedSpecChanged]．

[`java.util.Optional`](https://docs.oracle.com/javase/jp/15/docs/api/java.base/java/util/Optional.html) など一部の標準ライブラリクラスのAPIドキュメントでは，以前から値ベースのクラスであることが明記されています．

[^ValueBasedSpecChanged]: Java15までの規定とは少し変わっています．ラッパークラスを含めるために非推奨なコンストラクタが許可され，一方でプリミティブクラスに移行するためにインスタンスフィールドの制限が追加されています．



## 非互換な変更

Javaの標準ライブラリのうち値ベースであるものを，将来のバージョンでプリミティブクラスに変更することが予定されています．これは，これらのクラスが適切でない使われ方をしている場合に互換性のない変更となります．

プリミティブ型のラッパークラス(`java.lang.Integer`等)も，プリミティブクラスへの変更が予定されています．ラッパークラスは値ベースの要件の大半を満たしていますが，`public`なコンストラクタ[^wrapperConstructor]が存在します．

[^wrapperConstructor]: Java9から非推奨にはなっていました

プリミティブクラスへの変更により，次のような影響が想定されます．

+ `==` と書くと `equal` 相当の比較が行われる
+ `public`コンストラクタが削除される
+ 同期モニタに使用するとコンパイル時エラーあるいは実行時例外が発生する


### 既存コードへの影響


値ベースのクラスをプリミティブクラスに移行しても基本的には使う側への影響はないと考えられますが，次のような使い方をしている場合には注意が必要です:

+ 比較演算子`==` `!=` を使用している
    + 比較結果が変わります
+ ラッパークラスのコンストラクタ(`new Integer`等)を呼び出している
    + `LinkageError` が発生します
+ インスタンスを `synchronized` の対称として使用している
    + 例外が発生します


比較演算については自動的に不適切な使用を検出するのは難しいですが，あまり問題にはならないと考えられています．

## JDK16から追加される警告

16の時点では互換性を損なう変更はまだ入りません[^noSpecChange]が，ユーザー向けに新しい警告が出るようになります．

[^noSpecChange]: 言語仕様・仮想マシン仕様への変更はない

+ ラッパークラスのコンストラクタに関しては，従来から非推奨でしたがさらに`forRemoval=true`とすることで，コンパイル時にデフォルトで`"removal"`警告が表示されます．
+ 同期での使用に関しては，新しい警告カテゴリ`"synchronization"`が追加されました．`synchronized`文のオペランドの型[^operandStaticType]が値ベースのクラスであるか，全てのサブクラスが値ベースであるクラスの場合に警告が発生します．`"synchronization"`警告はコンパイル時にデフォルトで表示されます．
+ HotSpotは値ベースクラスのインスタンスに対する `monitorenter` の呼び出しを検出します．このとき，コマンドラインオプション `XX:DiagnoseSyncOnValueBasedClasses` によってこの事象を記録したりfatal errorにすることが出来ます．

[^operandStaticType]: 静的に決定できる範囲の型．Object型変数に代入してから使用している場合などはコンパイル時警告にはならない

## 値ベースのクラスの識別

コンパイラや仮想マシンが標準ライブラリ内の値ベースのクラス[^VBAnnotateTarget]には，`@jdk.internal.ValueBased`アノテーションが付与されます．対象クラスは以下の通りです．

[^VBAnnotateTarget]: 正確には，それ自体が値ベースであるクラス及び，全てのサブクラスが値ベースであるべきと規定されている抽象クラス・インターフェース


+ `java.lang` のプリミティブラッパークラス(`Byte`, `Short`, `Integer`, `Long`, `Float`, `Double`, `Boolean`, `Character`)
+ `java.lang.Runtime.Version` クラス
+ `java.util` のオプショナル系統のクラス(`Optional`, `OptionalInt`, `OptionalLong`, `OptionalDouble`)
+ `java.time` API の多くのクラス(`Instant`, `LocalDate`, `LocalTime`, `LocalDateTime`, `ZonedDateTime`, `ZoneId`, `OffsetTime`, `OffsetDateTime`, `ZoneOffset`, `Duration`, `Period`, `Year`, `YearMonth`, `MonthDay`)
+ `java.time.chrono` の一部のクラス(`MinguoDate`, `HijrahDate`, `JapaneseDate`, `ThaiBuddhistDate`)
+ `java.lang.ProcessHandler` インターフェースとその全ての実装クラス
+ `java.util` のコレクションファクトリ(`List.of`, `List.copyOf`, `Set.of`, `Set.copyOf`, `Map.of`, `Map.copyOf`, `Map.ofEntries`, `Map.entry`)で使用されるコレクションの実装クラス

`java.lang.constant` や `jdk.incubator,foreign` には以前から値ベースであると規定されていたクラス・インターフェースがありましたが，フィールドを継承しているなどJava16での値ベースの要件を満たさないため，プリミティブクラスには移行できません．このため，これらのクラスの仕様から「値ベースのクラスである」という記述は削除されました．

## 今後の経過措置について

今回警告を出し始めたところなので，既存の値ベースクラスをプリミティブクラスに移行するまでには，何回かのJavaリリースの間待つ必要があります．また，

+ ラッパークラスのコンストラクタ呼び出しを実行時に呼び出しを検出する手段
+ ラッパークラスのコンストラクタを呼び出している既存バイナリを利用できるようにする[^wrapperMiragtion]ためのツール

を別のJEPで提供することが計画されています．

[^wrapperMiragtion]: バイトコードを書き換えて`valueOf`呼び出しに変更するなど