はじめに
Java 17のソースコードで遊んでいると、下記コードのsynchronized
ブロックで警告が出ました。
public class SynchronizedSample {
Integer integerObject = 1;
void sampleMethod() {
synchronized(integerObject) {
System.out.println("a");
}
}
}
Integer is a value-based type which is a discouraged argument for the synchronized statement
「integer
はvalue-based
型でsynchronized
構文では推奨されないよ」ですって。
「なんじゃこりゃ」と思い調べてみたので記事にします。
Value-based ってなんやねん
Value-based Class
というものがあるそうです。
(↓我らがDeepL翻訳で訳してちょこちょこ手を加えただけ)
Value-based Class
は次のような特性をもちます。
- クラスは
final
なインスタンスのフィールドのみを宣言する(ただし、これらのフィールドには変更可能なオブジェクトへの参照が含まれている可能性がある)。 - クラスの
equals
、hashCode
、toString
の実装は、インスタンスのIDからではなく、クラスのインスタンス・フィールド(およびそれらが参照するオブジェクトのメンバ)の値のみから結果を計算する。 - クラスのメソッドは、インスタンスが等しい場合は自由に置換可能として扱う。つまり、
equals
で等しい2つのインスタンスx
とy
を入れ替えても、メソッドの動作に目に見える変化は生じない。 - クラスはインスタンスの
monitor
を使った同期を行わない。 - クラスは利用可能なコンストラクタを宣言していない(あるいは非推奨)。
- このクラスは、各メソッド呼び出し時に一意のIDを約束するインスタンス生成のメカニズムを提供しない。特に、ファクトリーメソッドの生成時は、独立に生成された2つのインスタンスが
equals()
に従って等しい場合、==
に従って等しくなる可能性を許容しなければならない。 - クラスは
final
である。 - クラスは
Object
か、インスタンスフィールドやインスタンス初期化子を宣言せずコンストラクタが空である抽象クラスの階層のいずれかを継承している。
例えば、java.lang.Integer
、java.time.LocalDate
のようなクラスです。
Value-based Class
の2つのインスタンスが(equals
に従って)等しいとき、プログラムは、参照の等価性によって直接的にであろうと、同期、IDハッシュ、直列化、あるいは他のIDに依存するメカニズムによって間接的にであろうと、それらのIDを区別しようと試みるべきではない。
なぜなら、プログラマーは関連するmonitor
の排他的な所有権を保証できないからである。
Value-based Class
の ID 関連の動作は、将来のリリースで変更される可能性がある。例えば、同期に失敗する可能性がある。
(翻訳ここまで)
要するに
Value-based Class
は一意のIDを約束するコンストラクタを提供せず、裏ではIDではなく値をもとにいろいろと操作が行われるオブジェクトのことを指すのでしょうかね。
なんで警告なのか
オブジェクトを一意に識別するIDを持たないValue Class
というものが導入されています(まだPreview版のはず)。
Value Class
はオブジェクトのIDを持たないためロックに使えません。
Some classes in the standard library have been designated value-based, with the understanding that they would become value classes in a future release.
...
In anticipation of this feature we already added warnings about potential behavioral incompatibilities for value class candidates in javac and HotSpot, via JEP 390.
Value-based Class
は将来のリリースでValue Class
になる可能性があります。
この機能を見越して、JEP 390を通じてjavacとHotSpotにおいてValue Class
の候補に対して潜在的な動作の非互換性に関する警告をすでに追加しています。
ということでした。
ちなみにValue Classとは(ざっくりと)
Value Class
はvalue
キーワードを使って宣言します。
value class USDollars implements Comparable<USDollars> {
private int cs;
private USDollars(int cs) { this.cs = cs; }
public USDollars(int dollars, int cents) {
this(dollars * 100 + (dollars < 0 ? -cents : cents));
}
public int dollars() { return cs/100; }
public int cents() { return Math.abs(cs%100); }
public USDollars plus(USDollars that) {
return new USDollars(cs + that.cs);
}
public int compareTo(USDollars that) { ... }
public String toString() { ... }
}
value
キーワードで宣言したクラスを==
で比較する時、フィールドの値を比較するようになります(プリミティブの場合はビットパターンで、その他は再帰的に==
で比較)。
USDollars d1 = new USDollars(3,95);
USDollars d2 = new USDollars(3,95).plus(new USDollars(0,0));
assert d1 == d2;
value
をつけないクラスの場合にはfalse
と評価されますが、クラスの宣言にvalue
をつけることでtrue
と評価されることが期待されます。
また、このようなValue Class
のインスタンスのことを「値オブジェクト(value object
)」と言います。
そもそも
Value-based Class
の特徴を考えると、@tchaikovsky1026さんのおっしゃる通りかなと思います。
Integerは値オブジェクトであり, インスタンスが色んなところで共有されて使われるのが普通(むしろ共有されるべき)なので, synchronized(Object)との相性は最悪だと思います.
おわりに
まちがっているところとか補足あれば、ご指摘いただけると嬉しいです。
(2023/12/8) コメントを投稿いただいた@tchaikovsky1026さん、@skrbさんありがとうございました。値オブジェクトというものを知りませんでした。勉強になりました