2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Java】compareToが不要な範囲チェック

Last updated at Posted at 2020-03-27

範囲チェックの実装、どうしてる?

数値やら文字の範囲チェックって、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(最大値, 最小値)でも、正しく範囲は設定されます。(getMaximumgetMinimumの値は崩れない)
範囲とした型と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クラスを使っていただきたいなと思うのです。

2
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?