HOWTO · Java
Java でセットを並べ替える
このチュートリアルでは、Java でセットを並べ替えます
このページの内容
HashSet は反復順序を保証しないため、要素がソート済みだとは見なせません。値をソートするには、セットを List にコピーしてそのコピーを並べ替えるか、値を TreeSet に格納するか、出力を作る過程でストリームをソートします。ソートにインデックスによるアクセスは必要ありません。ソート処理はコレクションまたはその要素を対象に行われます。ハッシュコードはハッシュ検索に使われ、一意な識別子ではありません。実装の詳細によっては HashSet の反復順序に影響することがありますが、Javaは HashSet の反復順序を保証していません。
Setを変換するか、順序付きの構造を使う理由
Set インターフェースが定めるのは要素の一意性であり、特定の順序ではありません。特に HashSet は挿入順とは異なる順序で反復されることがあり、セットの内容が変われば反復順序も変わる場合があります。デバッグ中や一度の実行でたまたま見えた順序を当てにしないでください。
ソートには順序を定める規則が必要です。数値や文字列なら、Javaの自然順序を使えることがよくあります。独自の型では自然順序を定義するか、用途に関係するフィールドを比較する Comparator を指定できます。必要な順序と後続処理に合わせて方法を選びましょう。ソート済みリストはインデックス操作やリスト固有の処理に便利です。TreeSet ならSetとしての一意性を保てます。ソート済みストリームは処理パイプラインの一部として利用できます。
SetをListにコピーしてソートする
要素を ArrayList にコピーし、そのリストに対して Collections.sort を呼び出します。元のセットは変更されず、選んだ順序で反復できるリストが得られます。この例では自然順序を持つ整数を使っています。別の型をソートする場合は、希望する順序を表すコンパレーターを指定してください。ここでは例を実行していません。説明はJavaの順序付けの動作に基づいています。
次の例を実行します。コード内の表示ラベルと出力は英語のままです。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<Integer> numbers = new HashSet<>(Arrays.asList(3, 6, 4));
List<Integer> sorted = new ArrayList<>(numbers);
Collections.sort(sorted);
System.out.println(sorted);
}
}
コピーしたリストでは、整数が昇順に並びます。リスト固有の操作が必要な場合や、元のセットの内容と表現を保ちたい場合に、この方法を使いましょう。
TreeSetで要素をソート済みに保つ
TreeSet は、自然順序または指定したコンパレーターに従って一意な要素を保持するクラスです。セットから構築すると要素がコピーされ、反復時にはその順序で取り出せます。元のセットは変更されません。コード内の表示ラベルと出力は英語のままです。
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
public class Main {
public static void main(String[] args) {
Set<Integer> numbers = new HashSet<>(Arrays.asList(3, 6, 4));
TreeSet<Integer> sorted = new TreeSet<>(numbers);
System.out.println(sorted);
}
}
反復では数値の自然順序が使われます。別の順序が必要なら、Comparator を TreeSet のコンストラクターに渡すこともできます。TreeSet は順序付けの規則に基づいて重複を判断します。コンパレーターの結果が0になる要素は、同じセット要素として扱われます。特に順序付けが equals と整合しない場合は、この比較が意図した一意性の規則に合っていることを確認してください。
ストリーム内でSetの要素をソートする
stream() の後に sorted() を呼び出すと、要素を順序付けて後続の処理に渡せます。元のセットの順序や内容は変更されず、パイプラインの後続処理に使うソート済みストリームが作られます。自然順序が適さない場合は、sorted(comparator) にコンパレーターを指定してください。この方法には、ストリームをサポートするプロジェクトの実行環境が必要です。コード内の表示ラベルと出力は英語のままです。
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<Integer> numbers = new HashSet<>(Arrays.asList(20, 4, 15));
numbers.stream().sorted().forEach(System.out::println);
}
}
ストリームは値を昇順に出力しますが、numbers は反復順序が保証されないセットのままです。forEach などでソート済みの値をすぐに処理する場合に便利です。後続のコードで再利用できるソート済みリストが必要なら、リストに変換してください。
方法の選び方と注意点
次の処理が List を必要とする場合、リスト固有の操作を使う場合、または元のセットを別に保ちたい場合は、コピーをソートします。後の反復でも順序を保ちながらSetの一意性を維持したい場合は TreeSet を使います。処理パイプライン内でソートし、その場で順序付けられた要素を処理すればよい場合は stream().sorted() を使います。
どの方法でも、要素を比較できる必要があります。適切な自然順序を使うか、コンパレーターを指定してください。TreeSet には、比較の結果がどの値を重複と見なすかにも影響するという追加の制約があります。一方、リストやストリームのソートでは、比較結果が同じ値が取り除かれることはありません。どの方法を使っても、元の HashSet が順序付きセットに変わるわけではありません。一度だけ表示順を整えたいなら、ほかの場所で使うコレクションを変更せず、コピーまたはストリームをソートしてください。