【Guava】RangeMapで範囲と値をスマートに管理

Javaで開発をしていると、「数値の範囲に応じて異なる値を割り当てたい」場面によく出会います。たとえば、スコアに応じた成績判定、時間帯ごとの料金設定、年齢別の区分けなどです。

こうした処理をif-elseswitch文で実装すると、条件が増えるにつれてコードが煩雑になり、保守も難しくなっていきます。

そこで活躍するのが、GoogleのGuavaライブラリが提供するRangeMapです。RangeMapを使えば、「範囲」に対してスマートに値をマッピングでき、ロジックを簡潔かつ見通しよく記述できます。

この記事では、RangeMapの基本的な使い方から応用例までを、実際のコードとともに分かりやすく解説していきます。

目次

RangeMapとは?

RangeMap<K extends Comparable, V>は、特定の「範囲(Range<K>)」に対して値(V)を関連付けるためのデータ構造です。範囲は連続する値の区間(例:0〜59、9:00〜18:00など)で表され、個別のキーではなく「区間」を使ってマッピングできるのが特徴です。

通常のMap<K, V>では、1つのキーにしか値を割り当てられず、範囲ベースの条件分岐はifswitch文に頼る必要があります。
しかしRangeMapを使えば、たとえば「60〜79点は可」「80〜100点は優」といったように、区間単位で値をマッピングできるため、コードの見通しが大きく改善します。

GuavaにはこのRangeMapの代表的な実装として、TreeRangeMapが用意されています。

RangeMap<Integer, String> rangeMap = TreeRangeMap.create();

このように、数値や時間など連続性のあるデータに対して直感的に値を割り当てたい場合、RangeMapは非常に有効な選択肢となります。

Range自体の詳細については、以下の記事をご覧ください。

RangeMapの基本的な使い方

RangeMapでは、範囲と値をセットで操作します。ここでは、主要なメソッドの使い方と特徴を実例とともに紹介します。

put(Range<K>, V):範囲に値を割り当てる

指定した範囲に値を設定します。既存の範囲と重なる場合、その重複部分は自動的に削除されて上書きされます

rangeMap.put(Range.closed(60, 79), "可");

putCoalescing(Range<K>, V):隣接する範囲を自動で結合して割り当てる

putと似ていますが、前後の隣接または重複する範囲で値が同じ場合、それらを1つの範囲に自動で統合します。

rangeMap.put(Range.closed(0, 9), "低");
rangeMap.put(Range.closed(10, 19), "低");
rangeMap.putCoalescing(Range.closed(19, 29), "低");
// → 結果: [0..9]=低, [10..29]=低

putAll(RangeMap<K, V>):他のRangeMapをまとめて追加

別のRangeMapに登録された範囲と値を一括で追加します。重複する範囲は上書きされるため、マージ前に重複範囲に注意が必要です。

RangeMap<Integer, String> otherRangeMap = TreeRangeMap.create();
otherRangeMap.put(Range.closed(90, 100), "満点");
rangeMap.putAll(otherRangeMap);

get(K):値を取得

指定されたキーが属する範囲の値を返します。該当する範囲がなければnullが返ります。

rangeMap.get(75); // → "可"

getEntry(K):範囲と値のペアを取得

指定キーが含まれる範囲とその値Map.Entry形式で取得できます。キーの範囲も含めて情報が欲しい場合に便利です。該当する範囲がなければnullが返ります。

Map.Entry<Range<Integer>, String> entry = rangeMap.getEntry(85);
System.out.println(entry.getKey());   // → [80..100]
System.out.println(entry.getValue()); // → "優"

remove(Range<K>):範囲の削除

指定した範囲に一致または部分的に重なるマッピングを削除します。
重なりがある場合は、その範囲だけが切り取られて消えます。

rangeMap.remove(Range.closed(70, 100));

merge(Range<K>, V, BiFunction<V, V, V>):値を合成して登録

重複範囲がある場合、既存の値と新しい値を関数で合成し、マッピングに登録します。
ログやラベルのように複数の情報を重ねたいときに便利です。

rangeMap.put(Range.closed(0, 9), "A");
rangeMap.merge(Range.closed(0, 9), "B", (oldVal, newVal) -> oldVal + "+" + newVal);
// → [0..9] = "A+B"

clear():全データ削除

マップに登録されたすべての範囲と値を削除します。

rangeMap.clear();

asMapOfRanges() / asDescendingMapOfRanges():読み取り用のMapを取得

現在の内容を、Range<K>をキーにしたMap形式で取得できます。asMapOfRanges()は昇順、asDescendingMapOfRanges()は降順になります。読み取り専用で、直接編集はできません。

Map<Range<Integer>, String> ascending = rangeMap.asMapOfRanges();
Map<Range<Integer>, String> descending = rangeMap.asDescendingMapOfRanges();

subRangeMap(Range<K>):部分ビューを取得

指定した範囲に含まれるマッピングだけを切り出したライブビューを返します。
このビューでの変更は、元のRangeMapにも反映されます。

RangeMap<Integer, String> subRangeMap = rangeMap.subRangeMap(Range.closed(60, 90));

span():登録済み範囲の全体を取得

現在登録されているすべての範囲を含む、最小~最大までのRangeを返します。

Range<Integer> span = rangeMap.span(); // → 例:[0..100]

空の場合はNoSuchElementExceptionがスローされるため、必要に応じてasMapOfRanges().isEmpty()で存在チェックを行ってください。

よくあるユースケース3選

RangeMapは、単なるコレクションの代替ではなく、「範囲ごとに異なる処理・値を割り当てる」場面で非常に効果を発揮します。ここでは、実際の開発でよくある代表的なユースケースを3つ紹介します。

1. テストスコアに応じた成績の自動判定

スコアの点数に応じて「不可」「可」「優」などの評価を行う処理は、学校や資格試験、社内ツールでも頻繁に登場します。RangeMapを使えば、条件分岐なしで一発で判定できます。

RangeMap<Integer, String> gradeMap = TreeRangeMap.create();
gradeMap.put(Range.closed(0, 59), "不可");
gradeMap.put(Range.closed(60, 79), "可");
gradeMap.put(Range.closed(80, 100), "優");

System.out.println(gradeMap.get(75)); // → 可

2. 時間帯ごとの料金プランの管理

タクシー、電力、宿泊など、時間帯によって料金が変わるサービスにおいて、RangeMapは明快かつ変更しやすい料金表を実現できます。
ここでは、3つの時間帯に分けて料金を設定する例を示します。

RangeMap<LocalTime, Integer> pricingMap = TreeRangeMap.create();
pricingMap.put(Range.closedOpen(LocalTime.of(6, 0), LocalTime.of(10, 0)), 1000);
pricingMap.put(Range.closedOpen(LocalTime.of(10, 0), LocalTime.of(14, 0)), 2000);
pricingMap.put(Range.closedOpen(LocalTime.of(14, 0), LocalTime.of(18, 00)), 1500);

System.out.println(pricingMap.get(LocalTime.of(11, 30))); // → 2000

3. IPアドレス範囲によるアクセス制御

特定のIPレンジにアクセス権限を割り当てたい場合、IPアドレスを数値(long)に変換してRangeMapで扱うと非常に効率的です。
たとえば、社内ネットワークは「internal」、パートナー企業は「trusted」といった区別が簡単に行えます。

RangeMap<Long, String> ipPolicyMap = TreeRangeMap.create();
ipPolicyMap.put(Range.closed(ipToLong("192.168.0.0"), ipToLong("192.168.0.255")), "internal");
ipPolicyMap.put(Range.closed(ipToLong("10.0.0.0"), ipToLong("10.255.255.255")), "trusted");

System.out.println(ipPolicyMap.get(ipToLong("192.168.0.100"))); // → internal

ipToLongメソッドは、IPv4アドレスをlongに変換するユーティリティとして実装しておくと便利です。

RangeMapのメリットと注意点

RangeMapを使うメリット

  • 条件分岐の簡素化
    複雑なif-elseswitch文を排除し、宣言的で読みやすいコードが書けます。
  • 直感的に範囲を扱える
    Rangeをキーとしてそのまま使えるため、範囲の定義や見直しが容易です。
  • 重複や隣接範囲の自動処理
    putCoalescingmergeなど、隣接範囲や重複時の処理も柔軟に対応できます。
  • ライブビューやMap形式でのアクセス
    asMapOfRangessubRangeMapにより、範囲の一覧表示や部分抽出も簡単です。

使用時の注意点

  • 重複範囲は上書きされる
    putを使うと、既存の範囲と重なった部分は自動的に削除されるため、意図しない上書きに注意が必要です。
  • null値は使用不可
    RangeMapにはnullを値として登録することはできません。必要に応じてプレースホルダ値などで代替しましょう。
  • 境界の定義に注意
    Range.closedRange.openなど、開始・終了点の含む/含まないは動作に大きく影響します。要件に応じて正確に使い分けましょう。

まとめ:RangeMapで範囲管理を直感的に

RangeMapを使えば、「数値や時刻などの連続する範囲」に対して値を割り当てる処理を、簡潔かつ明確に記述できます。

従来なら煩雑なif-elseswitchで実装していたロジックも、RangeMapを使うことで宣言的・構造的に整理でき、メンテナンス性が大きく向上します。

スコア判定や時間帯別処理、IPアドレス制御など、「範囲ベースの条件」を扱うすべての場面で、RangeMapは強力な武器になります。

コードの見通しや可読性に悩んだら、ぜひ一度RangeMapの導入を検討してみてください。
柔軟で直感的な範囲管理が、きっとあなたの開発を楽にしてくれるはずです。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次