Java Setで重複データを削除する方法!Listとの連携や変換を完全解説
生徒
「Javaでリストの中に同じ名前が何度も出てきて困っています。重複しているデータを一気に消す簡単な方法はありますか?」
先生
「それはJavaのSetインターフェースを使うのが一番の近道ですね。Setは数学の集合と同じで、同じ値を二つ持つことができないという性質があるんです。」
生徒
「なるほど!でも、普段はArrayListを使っているのですが、うまく連携できるのでしょうか?」
先生
「もちろんです。ListからSetへ、そしてまたListへ戻す手順を覚えれば、重複削除は一瞬で終わりますよ。具体的な手順を詳しく見ていきましょう!」
1. JavaのSetとは?重複を許さないコレクションの基本
Javaプログラミングにおいて、データをまとめて扱うための仕組みをコレクションと呼びます。その中でも「Set」は非常にユニークな存在です。最大の特徴は、同じ要素を複数格納することができない、つまり「重複を許さない」という点にあります。
例えば、買い物リストを作るときに、間違えて「りんご」を二回書いてしまったとします。Listの場合はそのまま二つの「りんご」が保存されますが、Setにそのリストを渡すと、自動的に一つにまとめられます。この性質を利用することで、膨大なデータの中からユニークな値だけを抽出する処理が驚くほど簡単に実装できるのです。
初心者の方がまず覚えるべきはHashSetというクラスです。これはSetインターフェースを実装した最も一般的なクラスで、処理速度が非常に速いというメリットがあります。データの順序を保持しないという特徴がありますが、重複削除が目的であれば最も適した選択肢となります。
2. ListからSetへ変換して重複を除去する仕組み
実務では、まずArrayListなどでデータを受け取り、その後に重複を消したいという場面が多くあります。Javaでは、ListのデータをSetのコンストラクタに渡すだけで、自動的に重複が取り除かれた状態のSetを作成することができます。
内部的な仕組みとしては、Setに要素を追加しようとする際、すでにある要素と同じかどうかをチェックしています。もし同じ値が存在すれば、新しい値は無視されます。この「同じかどうか」の判定には、equalsメソッドとhashCodeメソッドが使われていますが、String型やInteger型などの標準的なクラスを使う場合は、開発者が意識しなくても正しく判定してくれます。
それでは、実際にListにある重複データをSetを使って削除する最もシンプルなプログラムを見てみましょう。
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class DuplicateRemoveExample {
public static void main(String[] args) {
// 重複を含むリストを作成
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Apple");
fruits.add("Orange");
fruits.add("Banana");
System.out.println("元のリスト: " + fruits);
// ListをSetに変換して重複を削除
Set<String> fruitSet = new HashSet<>(fruits);
System.out.println("重複削除後のSet: " + fruitSet);
}
}
実行結果は以下のようになります。
元のリスト: [Apple, Banana, Apple, Orange, Banana]
重複削除後のSet: [Apple, Orange, Banana]
3. 重複削除したデータを再びListに戻す方法
Setで重複を削除できても、その後の処理でインデックス(添え字)を使って要素にアクセスしたい場合や、別のメソッドにList形式で渡さなければならない場合があります。その時は、Setを再びListに変換する必要があります。
この逆変換も非常に簡単です。ArrayListのコンストラクタに、先ほど重複を除去したSetを渡すだけです。この「List → Set → List」という流れは、Java開発において重複削除の定石(イディオム)として非常によく使われます。たった2行の記述で、複雑なループ処理や条件分岐を書くことなく、安全にデータをきれいにすることができます。
以下のコードでは、重複を消した後に再度Listとして扱う流れを確認しましょう。この方法は、記述がシンプルなのでバグが混入しにくいという大きなメリットがあります。
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ListToSetToList {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);
numbers.add(10);
numbers.add(30);
numbers.add(20);
// 1. Setに変換して重複削除
Set<Integer> set = new HashSet<>(numbers);
// 2. 再びListに変換
List<Integer> uniqueNumbers = new ArrayList<>(set);
System.out.println("重複削除後のリスト: " + uniqueNumbers);
}
}
重複削除後のリスト: [20, 10, 30]
4. データの順番を維持したい場合のLinkedHashSet
これまでの例で使用したHashSetは、データの格納順序を保証しません。実行結果を見ると分かる通り、追加した順番とは異なる並び順になることがあります。もし「重複は消したいけれど、最初に出現した順番は守りたい」という場合には、LinkedHashSetを使うのが正解です。
LinkedHashSetは、要素の重複を許さないというSetの機能に加え、要素が追加された順番を記録しておく機能を持っています。メモリの使用量はHashSetよりわずかに増えますが、近年のコンピュータのスペックであれば、数万件程度のデータなら全く気にする必要はありません。ユーザーインターフェースに表示するリストなど、並び順に意味があるデータを扱う際は、迷わずこちらを選択しましょう。
順序を維持する場合の挙動を、プログラムで確認してみましょう。
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
public class OrderedSetExample {
public static void main(String[] args) {
List<String> sequence = new ArrayList<>();
sequence.add("第一");
sequence.add("第二");
sequence.add("第一");
sequence.add("第三");
// LinkedHashSetを使うと順序が維持される
Set<String> orderedSet = new LinkedHashSet<>(sequence);
List<String> result = new ArrayList<>(orderedSet);
System.out.println("順序を維持した結果: " + result);
}
}
順序を維持した結果: [第一, 第二, 第三]
5. TreeSetを使ってデータを自動で並べ替える
重複を削除すると同時に、データを昇順(小さい順や辞書順)に並べ替えたい場面もあります。その際に便利なのがTreeSetです。TreeSetは、要素を追加するたびに自動的にソート(並べ替え)を行ってくれる非常に賢いクラスです。
例えば、ユーザーIDのリストから重複を消し、かつIDの若い順に並べたいときに威力を発揮します。ただし、TreeSetに格納するオブジェクトは、比較可能(Comparableインターフェースを実装している)である必要があります。StringやIntegerなどの基本クラスは標準で対応しているため、そのまま使うことができます。並べ替えのコストがかかるため、HashSetに比べると処理速度は少し落ちますが、自分でソート処理を書く手間に比べれば非常に効率的です。
6. Java 8以降のStream APIを活用したスタイリッシュな重複削除
モダンなJava開発では、コンストラクタを使った変換以外にも「Stream API」を利用した方法がよく使われます。Stream APIを使うと、データのフィルタリングや変換を流れるようなコードで記述でき、コードの可読性が向上します。
具体的には、`distinct()`というメソッドを使用します。このメソッドはその名の通り「はっきり区別された」要素、つまり重複を除いた要素だけを次に流す役割を持ちます。最後に`collect(Collectors.toList())`や`toList()`を呼び出すことで、直接Listとして結果を受け取ることができます。中間処理で他の条件(例えば「5文字以上の文字列だけ残す」など)を組み合わせることも容易なため、実務で最も好まれる手法の一つです。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamStreamExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("田中", "佐藤", "田中", "鈴木", "佐藤");
// Stream APIを使用して重複を削除
List<String> distinctNames = names.stream()
.distinct()
.collect(Collectors.toList());
System.out.println("Streamでの重複削除結果: " + distinctNames);
}
}
Streamでの重複削除結果: [田中, 佐藤, 鈴木]
7. 大規模データを扱う際のパフォーマンスと注意点
重複削除を行う際、データ量が数百万件を超えるようなケースでは注意が必要です。Setは内部的にハッシュテーブルという構造を使っていますが、初期容量が不足していると、データの追加に合わせて内部での再配置(リハッシュ)が発生し、処理が重くなることがあります。あらかじめデータ量が予想できる場合は、Setのインスタンス化の際に適切なサイズを指定することで、パフォーマンスを最適化できます。
また、自作したクラス(例:UserクラスやProductクラス)をSetで扱う場合は、必ずequalsとhashCodeを正しくオーバーライドしてください。これを忘れると、フィールドの値が全く同じであっても、別のインスタンスであれば重複とみなされず、削除に失敗してしまいます。EclipseやIntelliJ IDEAなどの開発ツールを使えば、これらのメソッドは自動生成できるため、必ず活用するようにしましょう。
8. まとめとしての実用的なTipsと選び方
Javaで重複データを削除する方法はいくつかありますが、状況に応じて最適なものを選ぶことが大切です。とにかく速く処理したい、順番はどうでもいいという場合は「HashSet」。元の順番を壊したくない場合は「LinkedHashSet」。アルファベット順や数字順に並んでほしい場合は「TreeSet」。そして、他のフィルタリング処理と組み合わせたい場合は「Stream API」を選択してください。
プログラミングの初心者にとって、コレクションの使い分けは最初の壁かもしれませんが、「重複させたくないならSet」という基本原則を覚えるだけで、コードの質は劇的に向上します。今回紹介したListとSetの連携テクニックは、業務システムからAndroidアプリ開発まで幅広く応用できる知識ですので、ぜひ自分のものにしてください。実際に手を動かしてコードを書いてみることで、より深い理解が得られるはずです。