数値の範囲を扱う処理は、意外と多くの開発シーンで登場します。たとえば、予約済みのIDの範囲を管理したり、使用中のポート番号を追跡したり、特定の時間帯の重複チェックを行ったり――こうした範囲管理を単純なList
やSet
で実装しようとすると、重複検出や隣接範囲の統合などで、煩雑なロジックが必要になります。
こうした煩雑さをスマートに解決してくれるのが、GoogleのGuavaライブラリが提供するRangeSet
です。複数の範囲を効率的に扱えるこのデータ構造を使えば、範囲の追加・削除・検索などを直感的に、かつ効率良く実装できます。
この記事では、RangeSet
の基本操作からユースケースまでを具体的なコード例とともに解説し、複雑になりがちな範囲管理をどうシンプルにできるかをご紹介します。
RangeSetとは?
RangeSet
は、Google Guavaが提供する複数のRange
(区間)を一括して管理するためのインターフェースです。特定の値が複数の範囲のどれかに属するか、重複や隣接する範囲をどう統合するかといった処理を、すっきりと実装できるのが特徴です。
代表的な実装クラスはTreeRangeSet
で、内部的にはTreeMap
を使って範囲を効率よく管理しています。主な特長は以下のとおりです。
- 重複や隣接範囲の自動統合:例えば
[1,5]
と(5,10)
を追加すると、重なりを考慮して1つの連続した範囲に統合されます。 - 特定の値や範囲に対する高速な判定:ある値が登録済みのどの範囲に含まれるかを素早くチェックできます。
- 柔軟な削除・分割処理:指定した範囲の一部だけを取り除くような操作も可能です。
このように、煩雑になりがちな範囲の集合処理を、安全かつ効率的に扱える便利なデータ構造です。
なお、各Range
オブジェクトは開区間・閉区間などの境界指定が可能で、より柔軟な条件を定義できます。Range
自体の詳細については、以下の記事をご覧ください。

RangeSetの基本的な使い方
ここでは、TreeRangeSet
を使った RangeSet
の主な操作を紹介します。
add(Range<C>)
:範囲の追加
新たな範囲を追加します。既存の範囲と重複・隣接している場合、自動で統合されます。
rangeSet.add(Range.closed(1, 5));
rangeSet.add(Range.open(5, 10));
remove(Range<C>)
:範囲の削除
指定した範囲を削除します。範囲の一部だけが重なっている場合、その部分だけを分割して除外します。
rangeSet.remove(Range.closed(3, 6));
addAll(RangeSet<C>)
/ addAll(Iterable<Range<C>>)
:複数範囲の追加
複数の範囲をまとめて追加します。統合処理も自動で行われます。
rangeSet.addAll(List.of(Range.closed(1, 2), Range.closed(3, 5)));
removeAll(RangeSet<C>)
/ removeAll(Iterable<Range<C>>)
:複数範囲の削除
複数の範囲をまとめて削除します。
rangeSet.removeAll(List.of(Range.closed(2, 3), Range.closed(4, 5)));
clear()
:全消去
すべての範囲を削除します。RangeSet
が空になります。
rangeSet.clear();
isEmpty()
:空チェック
RangeSet
が空かどうかを判定します。
boolean isEmpty = rangeSet.isEmpty();
contains(C)
:値の包含判定
指定した値が、いずれかの範囲に含まれていればtrue
を返します。
boolean included = rangeSet.contains(9);
rangeContaining(C)
:属する範囲の取得
指定した値が含まれるRange
を返します。なければnull
を返します。
Range<Integer> range = rangeSet.rangeContaining(7);
encloses(Range<C>)
:範囲の包含判定
指定した値が、現在のRangeSet
の中に完全に含まれているかを判定します。
boolean enclosed = rangeSet.encloses(Range.closed(3, 5));
enclosesAll(RangeSet<C>)
/ enclosesAll(Iterable<Range<C>>)
:複数範囲の包含判定
複数のRange
すべてがRangeSet
に含まれるかを判定します。
boolean allEnclosed = rangeSet.enclosesAll(List.of(Range.closed(2, 3), Range.closed(4, 5)));
intersects(Range<C>)
:範囲の交差チェック
指定した範囲と RangeSet
のいずれかの範囲が交差しているかを判定します。
boolean intersected = rangeSet.intersects(Range.closed(3, 5));
span()
:全体の範囲を取得
現在登録されているすべての範囲をカバーする、最小の1つの Range
を返します。
Range<Integer> span = rangeSet.span();
complement()
:補集合の取得
RangeSet
に含まれていない範囲(補集合)を取得します。空き領域の検出などに便利です。
RangeSet<Integer> complement = rangeSet.complement();
asRanges()
/ asDescendingSetOfRanges()
:すべての範囲を取得
現在の RangeSet
に含まれるすべての Range
を集合として取得できます。asRanges()
は昇順、asDescendingSetOfRanges()
は降順になります。
Set<Range<Integer>> ascending = rangeSet.asRanges();
Set<Range<Integer>> descending = rangeSet.asDescendingSetOfRanges();
subRangeSet(Range<C>)
:部分ビューの作成
指定した範囲に含まれる RangeSet
の部分ビューを返します。このビューは元のセットと連動しており、変更は反映されます。
RangeSet<Integer> subRangeSet = rangeSet.subRangeSet(Range.closed(3, 7));
ユースケースで学ぶRangeSetの活用
RangeSet
は、単なる技術的な構造にとどまらず、日常的な開発課題をスマートに解決してくれます。ここでは代表的な3つのユースケースを紹介します。
1. 使用中ポートの管理
ネットワークアプリケーションなどで「既に使われているポート番号の範囲」を管理する場面は多くあります。RangeSet
を使えば、使えるポート番号かどうかを簡単に判定できます。
RangeSet<Integer> usedPorts = TreeRangeSet.create();
usedPorts.add(Range.closed(8000, 8010));
usedPorts.add(Range.closed(8020, 8030));
boolean canUse = !usedPorts.contains(8015); // trueなら未使用ポート
2. 空き時間スロットの抽出
予約システムやスケジューラーでは、空いている時間帯の算出がよく必要になります。RangeSet
に予約済みの時間を登録し、complement()
を使うことで、空き時間の自動計算が可能です。
RangeSet<LocalTime> reserved = TreeRangeSet.create();
reserved.add(Range.closed(LocalTime.of(9, 0), LocalTime.of(10, 0)));
reserved.add(Range.closed(LocalTime.of(13, 0), LocalTime.of(14, 0)));
RangeSet<LocalTime> available = reserved.complement();
このようにすることで、「空いている時間」だけを取り出すことができます。
3. ID範囲によるブラックリスト管理
不正利用のあったユーザーIDなどを「範囲」でまとめて管理したい場合、RangeSet
を使うと判定が簡単です。大量のIDを1つずつ持たなくても、範囲指定だけでブロックできます。
RangeSet<Long> blacklist = TreeRangeSet.create();
blacklist.add(Range.closed(1000L, 1999L));
blacklist.add(Range.closed(3000L, 3999L));
if (blacklist.contains(1500L)) {
System.out.println("このIDは使用できません");
}
個別チェックの手間を省き、柔軟にブラックリストを構築できます。
パフォーマンスの特徴
RangeSetの主な操作の時間計算量は以下の通りです。
- 追加/削除: O(log n)
- 包含チェック: O(log n)
- 範囲取得: O(log n)
ここで、nは管理している範囲の数です。大量の範囲を扱う場合でも、効率的に処理できます。
まとめ:複雑な範囲管理もRangeSetでシンプルに
Guavaの RangeSet
を使えば、範囲の追加・削除・検索・統合といった処理を、統一されたインターフェースで直感的に記述できます。自前で複雑なロジックを組む必要がなくなるため、コードの可読性と保守性が大幅に向上します。
特に、数値・時間・IDなど、連続性のあるデータを扱う場面では、その効果が顕著です。複雑な境界条件や範囲の重なりも、RangeSet
が自動で処理してくれるため、実装ミスのリスクも低減できます。
「範囲」という概念がコード内で頻出する場合は、RangeSet
の導入をぜひ検討してみてください。シンプルで洗練された実装が、開発体験を一段と快適にしてくれるはずです。