例えば、「何かのリストなどからランダムに選び、すでに選んだものをSetに入れる」ということをする場合、
「選んだものがSetに無ければ追加、あればエラーメッセージなどの処理を行う」というのは割とありそう。
これは直感的には
Set<Integer> set = new HashSet<>();
for (int i = 0; i < 10; i++){
int r = (int)(Math.random() * 10);
if (!set.contains(r)) {
set.add(r);
} else {
System.out.println(r + "はすでに入っている");
}
}
このように、入れる前にcontainsで調べる、と書きたくなる。
しかし、実はこんな書き方もできる。
Set<Integer> set = new HashSet<>();
for (int i = 0; i < 10; i++){
int r = (int)(Math.random() * 10);
if (!set.add(r)) {
System.out.println(r + "はすでに入っている");
}
}
入っているかどうかを事前にチェックせず、いきなりaddし、それをifに入れることができるのである。
実はあまり日の目を見ることはないが、SetをはじめとするCollectionインタフェースのaddメソッドはbooleanを返す。
内容を見ると、
指定された要素がこのコレクションに格納されていることを保証します(オプションの操作)。この呼出しの結果、コレクションが変更された場合はtrueを返します。このコレクションが要素の重複を許可せず、指定された要素がすでに含まれている場合はfalseを返します。
addは「追加する」メソッドではなく、「その要素があることを保証する」メソッドであり、その前後でコレクションが変わったかどうかの結果を返す。
Setの場合は入れようとした要素がすでにある場合は追加しない。つまりSetが変更されることはないため、falseを返す。これがそのまま「入っていたかどうか」の判定に使えるわけだ。同時に、無かった場合はすでに追加を完了しているため、「無かった場合」に追加する処理を改めて書く必要がなくなる。
とはいえ
直感的に読みにくいコードであることには違いないだろう。Set以外にこのCollection#addの性質を活かせる場面はあるのだろうか…?