はじめに
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さんありがとうございました。値オブジェクトというものを知りませんでした。勉強になりました