範囲チェックの実装、どうしてる?
数値やら文字の範囲チェックって、obj.compareTo(value)
の組み合わせで書いてあることが多いですよね。
ですがそのcompareToの戻り値のパターンがどうしても覚えられないんです…
覚えてるのは、一致が「0」ということだけ。> 0
と< 0
のどっちが小さい/大きいなのか、毎回実行して確認しています…
でも、compareToメソッドや等号・不等号演算子による「最小値以上かつ最大値以下」の範囲チェックを書かなくて済む方法があったんです。
やはりApacheCommonsはぐう優秀
org.apache.commons.lang3.Range
クラス(結構昔からあるのに無知でした)がポンコツSEの救世主でした…これは推せる。
「java 範囲 チェック」でググると、等号・不等号演算子やcompareToによる実装が検索結果の上位にいるので、「commons」とかキーワードをちょっと工夫しないとなかなか辿り着けないんですよね、Rangeクラスには。
サンプルコード
int num0 = 0;
int num1 = 1;
int num2 = 2;
// 範囲オブジェクトを作成
Range rng = Range.between(num0, num2);
System.out.println("範囲内チェック(" + num1 + ") = " + rng.contains(num1));
System.out.println("範囲内チェック(" + -1 + ") = " + rng.contains(-1));
// Rangeなら範囲チェックだけじゃなくて、いろいろできます
System.out.println("範囲の最小値 = " + rng.getMinimum() + "、範囲の最大値 = " + rng.getMaximum());
System.out.println("範囲が引数より後かチェック(" + -1 + ") = " + rng.isAfter(-1));
System.out.println("範囲が引数より後かチェック(" + num0 + ") = " + rng.isAfter(num0));
// 変数に格納しなくてもメソッドチェーンでも可
System.out.println("範囲内チェック(" + num1 + ") = " + Range.between(num0, num2).contains(num1));
実行結果
範囲内チェック(1) = true
範囲内チェック(-1) = false
範囲の最小値 = 0、範囲の最大値 = 2
範囲が引数より後かチェック(-1) = true
範囲が引数より後かチェック(0) = false
範囲内チェック(1) = true
上記サンプルの他、isBefore
やcompareTo的な戻り値を返すelementCompareTo
等いろんなメソッドが実装されています。
ちなみにbetweenの最小と最大の値の型が不一致の場合はコンパイルエラー、片方でもnullの場合はIllegalArgumentExceptionが発生します。
また、betweenに渡す最小値と最大値の順番は関係なく、between(最大値, 最小値)
でも、正しく範囲は設定されます。(getMaximum
やgetMinimum
の値は崩れない)
範囲とした型とcontainsの引数の型が合わない場合は、ClassCastExceptionが発生します。
ジェネリクスの恩恵
RangeクラスはcompareToからの脱却というだけで最高なんですけど、素晴らしいのはそれだけじゃないんです。
上記の例はプリミティブのint型(Integerへの変換不要)でしたが、例えば文字列型だって日付や時刻型だって渡せちゃう!Rangeまじ愛してる。
// 日付型(LocalDate)のサンプル
LocalDate ld0 = LocalDate.now().minusDays(1);
LocalDate ld1 = LocalDate.now();
LocalDate ld2 = LocalDate.now().plusDays(1);
Range rng = Range.between(ld0, ld2);
System.out.println(rng.contains(ld0)); // true
System.out.println(rng.contains(ld1)); // true
System.out.println(rng.contains(ld2)); // true
System.out.println(rng.contains(ld2.plusDays(1))); // false
System.out.println(rng.isAfter(ld0.minusDays(1))); // true
System.out.println(rng.contains(null)); // false
もっと早く知りたかった…最の高じゃん…
betweenには、Comparatorクラスのオブジェクトを渡すこともできるので、自作オブジェクトの比較も可能です。
もうほんと最高of最高。ポンコツ具合がちょっと改善される気がします。
compareToの戻り値が覚えられないポンコツSEはそんなに多いわけではないと思うのですが、Rangeクラスを使うほうが見た目もすっきりわかりやすいと思います。contains
使うと論理演算子も不要ですし。
なので、これからの範囲チェックの実装にはぜひRangeクラスを使っていただきたいなと思うのです。