SOURCE
確認したい最小の Java コードだけを置きます。
Java 21 / JUnit 5
説明より先に、実行できる仕様を見る。Java by Tests は、Source と JUnit を並べて読むための Executable Documentation です。
package examples.string;
public final class StringComparison {
private StringComparison() {}
public static boolean sameReference(String left, String right) {
return left == right;package examples.string;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
class StringComparisonTest {
@TestObservation protocol
確認したい最小の Java コードだけを置きます。
JUnit の assertion で、挙動をそのまま固定します。
補足は短く。結果からコードへ戻るためだけに使います。
First verified proof
同じ文字列でも、参照と値の比較は別の契約です。
package examples.string;
public final class StringComparison {
private StringComparison() {}
public static boolean sameReference(String left, String right) {
return left == right;
}
public static boolean sameValue(String left, String right) {
return left.equals(right);
}
}package examples.string;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
class StringComparisonTest {
@Test
void 同じ内容でも別のStringなら参照は等しくない() {
var literal = "java";
var copied = new String("java");
assertFalse(StringComparison.sameReference(literal, copied));
}
@Test
void 同じ内容のStringはequalsで等しい() {
var literal = "java";
var copied = new String("java");
assertTrue(StringComparison.sameValue(literal, copied));
}
}Verified index
同じ文字列でも、参照と値の比較は別の契約です。
Java 8+→02isBlank は空文字だけでなく、空白文字だけの String も空白として扱います。
Java 11+→03append は新しい StringBuilder を作らず、呼び出し元を更新して同じ参照を返します。
Java 8+→04mutable な List では、add の呼び出しが同じインスタンスを更新します。
Java 8+→05List.of が返すコレクションは、変更操作を受け付けません。
Java 9+→06Arrays.asList は配列をコピーせず、要素の置換は共有しながら追加・削除を許可しません。
Java 8+→07キーがない場合にだけ、計算した値を登録して返します。
Java 8+→08Integer の参照比較は値ではなくキャッシュされた同じオブジェクトかを見ます。
Java 8+→091.0 と 1.00 は数値として同じでも、equals では scale の違いにより等しくありません。
Java 8+→10record は構成要素に基づく equals と hashCode を自動的に持ちます。
Java 16+→11equals が等しいオブジェクトは、同じ hashCode を返さなければ集合検索の契約を満たせません。
Java 8+→12値がない場合だけ、指定した既定値を選びます。
Java 8+→13orElse は値があっても引数を評価し、orElseGet は必要になるまで Supplier を呼びません。
Java 8+→14関数が Optional を返すとき、map は二重にし、flatMap は一段だけ平坦化します。
Java 8+→15filter は元の List を変更せず、条件を満たす要素だけを流します。
Java 8+→16map は Stream を組み立てただけでは実行されず、toList のような終端操作で初めて評価されます。
Java 8+→17Stream.toList の結果は、内容が同じでも add を受け付けない List です。
Java 16+→18失敗する入力に対して、期待する例外型をテストで固定します。
Java 8+→19IOException は呼び出し側で扱うことを型で求め、IllegalArgumentException は実行時に送出します。
Java 8+→20上限境界ワイルドカードで、Number の各サブタイプを読み取れます。
Java 8+→21extends はサブタイプから読み、super はスーパタイプのコンテナへ値を書き込みます。
Java 8+→22LocalDate は日付だけ、LocalDateTime は日付と時刻を持ち、変換時に失われる・補われる情報があります。
Java 8+→23AutoCloseable を複数宣言すると、ブロック終了時には宣言と逆順にcloseされます。
Java 7+→24hashCodeに使う状態を変更すると、HashMapが値を置いたバケットと検索先が一致しなくなります。
Java 8+→25割り切れない除算は、scaleとRoundingModeを明示しなければ期待する桁と方向を固定できません。
Java 8+→26nullを受け入れる境界ではofNullableを使い、nullが設計違反ならofで早く失敗させます。
Java 8+→27同じhashCodeのキーは共存できますが、equalsがtrueなのにhashCodeが違うキーでは登録済みの値を検索できません。
Java 8+→28new BigDecimal(0.1d)は2進浮動小数点の値を正確に取り込み、10進数の0.1とは一致しません。
Java 8+→29Optional型のフィールドや引数にもnullを渡せるため、不在の表現がnullとemptyの二つに増えます。
Java 8+→30絵文字のような補助文字はUTF-16で2単位になるため、lengthと文字数が一致しないことがあります。
Java 8+→31合成済み文字と結合文字の列は、表示が同じでも別のコードポイント列として比較されます。
Java 8+→32HashMapはnull値を保存できるため、getの戻り値がnullでもキーが存在しないとは限りません。
Java 8+→33List.ofは変更不可であるだけでなく、作成時にnull要素を検出して例外にします。
Java 9+→34toListやcountなどの終端操作を一度実行したStreamは、二回目の走査で例外になります。
Java 8+→35夏時間の切替日では、暦日を一つ進めるplusDaysと24時間を足すplusHoursが異なるローカル時刻になります。
Java 8+→36recordが生成するequalsでも、配列要素は内容ではなく配列オブジェクトの参照として比較されます。
Java 16+→37sealed型の許可サブタイプを全て扱うswitchは、defaultなしで分岐の網羅性を表せます。
Java 21+→38親スレッドで設定した通常のThreadLocal値は、新しく開始したvirtual threadではnullになります。
Java 21+→39非同期処理が失敗すると、joinは元の例外をそのままではなくCompletionExceptionのcauseとして送出します。
Java 8+→40HashMapと異なり、ConcurrentHashMapはnullキー・null値のputを即座に例外にします。
Java 8+→41ArrayList<String>とArrayList<Integer>はコンパイル時には別の型ですが、実行時クラスは同じです。
Java 8+→42TreeSet は equals ではなく Comparator の比較結果で、要素の重複と検索を判断します。
Java 8+→43List.copyOfで受け取ったListをコピーすると、呼び出し元による後続変更が内部状態へ漏れません。
Java 10+→44unmodifiable view は元のListを読み続け、List.copyOfの結果は作成時点の独立した値です。
Java 10+→45toMapは同じキーを見つけると、merge関数を指定していない限り例外にします。
Java 8+→46enumの各定数は一意なインスタンスであり、同じ定数かの比較には==を使えます。
Java 8+→47RetentionPolicy.RUNTIMEを指定したannotationだけが、実行時のreflection APIで取得できます。
Java 8+→48クラス初期化ではstatic fieldの初期化式がソース上の宣言順に評価されます。
Java 8+→49shortをlongとIntegerのoverloadへ渡すと、boxingではなくprimitive wideningのlongが選ばれます。
Java 8+→50既存配列をvarargs引数へ渡すと、メソッドはコピーではなくその配列を操作します。
Java 8+→51Integerをintとして使う式では自動unboxingが起き、nullなら演算前にNullPointerExceptionになります。
Java 8+→52同じStringリテラルはpoolの参照を共有し得ますが、new Stringは別インスタンスを作ります。
Java 8+→53submitしたタスクの例外は呼び出し元へ直ちに投げられず、Future.getでExecutionExceptionとして観測します。
Java 8+→54Thread.interruptedは現在の割り込み状態を返した後、そのフラグをclearします。
Java 8+→55synchronizedメソッドが同じオブジェクトの別のsynchronizedメソッドを呼んでも、同じスレッドなら停止しません。
Java 8+→