カテゴリ: Java 更新日: 2026/07/08

JavaのStreamメソッドdistinctをやさしく解説!初心者向け実例で重複削除を理解

Streamのdistinctメソッド
Streamのdistinctメソッド

先生と生徒の会話形式で理解しよう

生徒

「Javaで同じ値が何度も入っているリストから重複を消したいです。配列やリストに対して簡単に使える方法はありますか?」

先生

「java.util.streamのStreamにあるdistinctを使うと、重複を取り除いた流れを簡単に作れます。順番も保ちやすく、読みやすい書き方になりますよ。」

生徒

「どんな型でも使えますか?文字列や数値だけでなく、クラスのオブジェクトでも使えるのか気になります。」

先生

「使えます。ただしオブジェクトでは同一性の判定方法としてequalsとhashCodeの実装が関係します。まず基本から順に見ていきましょう。」

1. distinctとは何か

1. distinctとは何か
1. distinctとは何か

distinctは、流れの中に同じ値が複数存在するときに、最初に現れた一つだけを残し、それ以外の重複を取り除いてくれる操作です。元の並び順を壊さずに重複削除ができるため、後続の処理がとても読みやすくなります。従来の手続き型の書き方では、補助の集合や条件分岐を用意して複雑になりがちでしたが、distinctを使うと意図がひと目で伝わる表現にまとまります。文字列や数値などの基本的な型はもちろん、独自のクラスでも活用でき、収集や表示、集計の前処理として幅広く使われます。

2. 文字列の重複を消す基本例

2. 文字列の重複を消す基本例
2. 文字列の重複を消す基本例

まずは文字列の重複削除です。同じ単語が何度も入っているリストから重複を取り除き、順番を保ったまま一覧にできます。短いコードで誤りにくい表現になるため、最初の練習におすすめです。


import java.util.List;
import java.util.stream.Collectors;

public class DistinctStringBasic {
    public static void main(String[] args) {
        List<String> words = List.of("りんご", "みかん", "りんご", "ぶどう", "みかん");

        List<String> unique = words.stream()
                .distinct()
                .collect(Collectors.toList());

        System.out.println(unique);
    }
}

[りんご, みかん, ぶどう]

並び順を保ったまま、最初に現れた要素だけが残ります。そのままsortedやlimitなどへつなげても読みやすさが崩れません。

3. 大文字小文字を無視して重複を消す

3. 大文字小文字を無視して重複を消す
3. 大文字小文字を無視して重複を消す

実務では大文字小文字の揺れをまとめたい場面があります。distinctは値の同一性で判定するため、そのままだと大小の違いは別扱いになります。そこで流れの前段で小文字へ正規化し、重複削除を行います。意図が明確で、あとから読み返しても迷いにくい形です。


import java.util.List;
import java.util.stream.Collectors;

public class DistinctIgnoreCase {
    public static void main(String[] args) {
        List<String> words = List.of("Java", "java", "JAVA", "Stream");

        List<String> unique = words.stream()
                .map(s -> s.toLowerCase())
                .distinct()
                .collect(Collectors.toList());

        System.out.println(unique);
    }
}

[java, stream]

このように前段の加工と組み合わせることで、要件に合わせた重複判定が実現できます。先に掃除をしてからdistinctという順序はよく使われます。

4. オブジェクトの重複削除とequalsやhashCodeの関係

4. オブジェクトの重複削除とequalsやhashCodeの関係
4. オブジェクトの重複削除とequalsやhashCodeの関係

独自クラスの重複削除では、要素が同じかどうかの判断基準をequalsとhashCodeで定義しておくのが基本です。同じ識別子を持つなら同一とみなす、といった設計を明確にしておくと、distinctが期待どおりに働きます。これらが未定義のままだと、インスタンスが別物として扱われ、重複が残ってしまうことがあります。識別子と同値の概念をそろえておくと、読み手が迷わないコードになります。


import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

class Product {
    final String code;
    final String name;
    Product(String code, String name){ this.code = code; this.name = name; }
    @Override public boolean equals(Object o){
        if(this == o) return true;
        if(!(o instanceof Product)) return false;
        Product p = (Product)o;
        return Objects.equals(code, p.code);
    }
    @Override public int hashCode(){ return Objects.hash(code); }
    @Override public String toString(){ return code + ":" + name; }
}

public class DistinctObjectById {
    public static void main(String[] args) {
        List<Product> list = List.of(
                new Product("A001", "りんご"),
                new Product("A001", "りんご特選"),
                new Product("B010", "みかん")
        );
        List<Product> unique = list.stream()
                .distinct()
                .collect(Collectors.toList());
        System.out.println(unique);
    }
}

[A001:りんご, B010:みかん]

識別子を基準に同一性を定めておくと、余計な条件分岐を避けられます。設計の意図がコードに現れるため、保守での混乱も防げます。

5. 特定のキーで重複を消す設計

5. 特定のキーで重複を消す設計
5. 特定のキーで重複を消す設計

複数のフィールドを持つオブジェクトで、equalsを変更したくない場合もあります。そのときは一時的にキーへ写像してdistinctを適用し、最後に元へ戻す構成が有効です。たとえばコードだけで重複を消し、最初に現れた明細を残したいといった要件で使えます。


import java.util.LinkedHashMap;
import java.util.List;
import java.util.stream.Collectors;

public class DistinctByKey {
    public static void main(String[] args) {
        record Item(String code, String label, int price){}
        List<Item> items = List.of(
                new Item("A", "一番", 100),
                new Item("A", "二番", 120),
                new Item("B", "三番", 200)
        );

        List<Item> unique = items.stream()
                .collect(Collectors.toMap(
                        Item::code,
                        i -> i,
                        (first, dup) -> first,
                        LinkedHashMap::new))
                .values().stream().collect(Collectors.toList());

        System.out.println(unique);
    }
}

[Item[code=A, label=一番, price=100], Item[code=B, label=三番, price=200]]

写像と収集を組み合わせると、元のequalsに手を付けず要件どおりの重複排除ができます。順序を守れる点も実務で扱いやすい利点です。

6. mapやfilterとの組み合わせ

6. mapやfilterとの組み合わせ
6. mapやfilterとの組み合わせ

distinctは前後の操作と相性が良く、正規化や条件の絞り込みと組み合わせると意図が明確になります。たとえば空の値を除外してから重複を消し、その後で並べ替えや集計につなげると、処理の段取りが読みやすい形に整理されます。段階を小さく区切るほど、変更にも強くなります。


import java.util.List;
import java.util.stream.Collectors;

public class DistinctWithFilter {
    public static void main(String[] args) {
        List<String> raw = List.of("  りんご", "", "みかん", "りんご  ", "  ");
        List<String> cleaned = raw.stream()
                .map(s -> s.trim())
                .filter(s -> !s.isEmpty())
                .distinct()
                .collect(Collectors.toList());
        System.out.println(cleaned);
    }
}

[りんご, みかん]

前段で整えてからdistinctという順序が、最も素直で理解しやすい流れになります。保守や拡張の際にも迷いにくい構成です。

7. flatMapとdistinctで全体から一意な値を集める

7. flatMapとdistinctで全体から一意な値を集める
7. flatMapとdistinctで全体から一意な値を集める

入れ子の構造ではflatMapで平らにしてからdistinctを使います。全体の一覧から重複のない集合を得る手順が自然に表現でき、見通しの良いコードになります。分割や整形を先に済ませておくと、重複判定の基準がそろうため、漏れや二度手間を防げます。


import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class DistinctWithFlatMap {
    public static void main(String[] args) {
        List<String> lines = List.of("red, green", "blue, red", "yellow");

        List<String> colors = lines.stream()
                .flatMap(s -> Arrays.stream(s.split(",")))
                .map(String::trim)
                .distinct()
                .collect(Collectors.toList());

        System.out.println(colors);
    }
}

[red, green, blue, yellow]

展開、整形、重複削除という段取りを順に並べるだけで、やりたい処理の全体像が伝わります。流れの書き方がそのまま設計の説明になっています。

8. 並列処理と性能の考え方

8. 並列処理と性能の考え方
8. 並列処理と性能の考え方

distinctは内部で既出の要素を記録して判定します。大量のデータでは記録のコストが関係するため、前段で不要な要素を早めに捨てると効率が上がります。並列処理では順序維持のコストも影響するので、順序が不要なら無秩序の収集を検討するなど、要件に合わせて調整すると良い結果が得られます。可視化のためにpeekを挟んで流れを確かめ、ボトルネックの位置を把握してから最適化へ進むやり方が安全です。

9. つまずきやすい失敗例と回避策

9. つまずきやすい失敗例と回避策
9. つまずきやすい失敗例と回避策

オブジェクトの重複が消えないのは、equalsやhashCodeが未定義か、基準が一致していない場合が多いです。識別子を決め、それに基づいて同一性を定義しておくと安定します。大小の揺れや空白の混入、記号の差異などは、前段の正規化や整形を徹底してからdistinctを適用するだけで解決できます。また、先にソートを行うと残す要素の位置が変わるため、保持したい方を先に確定してから操作の順序を決めると、意図しない結果を避けられます。

10. 学習と実務での使い分けのコツ

10. 学習と実務での使い分けのコツ
10. 学習と実務での使い分けのコツ

学習ではまず基本の重複削除、次に正規化を伴う例、最後にオブジェクトとキーの写像という順で練習すると、distinctの考え方が身につきます。実務では、操作の順序を意識しながら前処理と重複削除を分けて書くと、仕様変更に強くなります。設計方針としては、判定基準を一か所へ集約する、責任を関数へ逃がさない、表示の整形は最後に回す、といった約束事を決めておくと、チームでの開発でも迷いを減らせます。distinctは短いメソッドですが、流れの設計を素直に表現できる頼もしい道具です。

まとめ

まとめ
まとめ

distinctの基本と実務での価値を振り返る

今回はJavaのStreamに用意されているdistinctメソッドについて詳しく学びました。distinctはコレクションや配列の中に含まれる重複データを取り除くための機能であり、シンプルでありながら非常に実用的な役割を持っています。特に初心者にとっては、重複削除という処理を短いコードで表現できる点が大きなメリットであり、プログラムの可読性を高める重要な要素となります。

従来の書き方では、重複を取り除くために補助的な集合を用意したり、条件分岐を組み合わせたりする必要がありましたが、distinctを使うことでそのような複雑な処理を一行で表現できます。このように処理の意図が明確になることで、コードを読む人にも分かりやすくなり、保守や改修の際にも理解しやすい構造になります。

また、distinctは単独で使うだけでなく、mapやfilterなどの他のStream操作と組み合わせることでさらに強力になります。前処理でデータを整形し、その後で重複を削除することで、より正確で意味のあるデータ処理が可能になります。こうした流れを意識することで、実務に近いプログラム設計ができるようになります。

重複削除の流れを整理する

distinctを効果的に使うためには、処理の順序がとても重要です。まずは不要な空白や記号を取り除き、データを整形します。そのうえでdistinctを適用することで、正しい基準で重複を判定できます。最後に並び替えや集計を行うことで、全体の処理が自然な流れになります。

このように段階ごとに処理を分けて書くことで、プログラムの意図が明確になり、後から修正する場合にも影響範囲を小さくすることができます。特にチーム開発では、このような読みやすい構成が重要になります。

復習用サンプルコード

ここでdistinctの基本的な使い方を確認するために、数値の重複を削除して合計を求める例を見てみましょう。


import java.util.List;

public class DistinctReviewExample {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(10, 20, 10, 30, 20);

        int total = numbers.stream()
                .distinct()
                .mapToInt(n -> n)
                .sum();

        System.out.println(total);
    }
}

60

このようにdistinctとmapToIntを組み合わせることで、重複を取り除いたうえで効率的に数値計算を行うことができます。

オブジェクトでの注意点

独自クラスを扱う場合には、equalsとhashCodeの実装が非常に重要になります。これらが適切に定義されていないと、同じ内容のデータであっても別のオブジェクトとして扱われ、重複削除が正しく行われません。そのため、どの項目を基準に同一とみなすのかを設計段階で明確にしておく必要があります。

また、特定の項目だけで重複を判定したい場合には、キーに変換してからdistinctを適用するなどの工夫も有効です。このような柔軟な対応ができる点も、Streamの大きな特徴です。

実務での活用ポイント

実際の開発では、ユーザー一覧の重複排除や商品データの整理、ログデータの集計など、重複削除が必要になる場面が多くあります。distinctを使うことで、こうした処理を簡潔に記述でき、開発効率を向上させることができます。

さらに、flatMapやfilterと組み合わせることで複雑なデータ構造にも対応できるため、応用範囲は非常に広いです。初心者のうちは基本的な使い方から始め、徐々に組み合わせのパターンを増やしていくと理解が深まります。

先生と生徒の振り返り会話

生徒

「distinctを使うと重複削除がとても簡単になることがわかりました」

先生

「処理の流れがそのままコードになるので読みやすくなります」

生徒

「前処理と組み合わせるのが大事なんですね」

先生

「その通りですデータを整えてから重複を消すと正しい結果になります」

生徒

「オブジェクトの場合はequalsにも注意します」

先生

「同一性の定義を明確にすることが安定したプログラムにつながります」

カテゴリの一覧へ
新着記事
New1
Thymeleaf
Thymeleafの#mapsユーティリティとは?初心者向けマップ操作ガイド
更新記事
New2
Thymeleaf
Thymeleafの#arraysユーティリティとは?初心者向け配列操作ガイド
更新記事
New3
Thymeleaf
Thymeleafの#strings.sizeメソッドとは?初心者向け文字列の長さ取得ガイド
更新記事
New4
Thymeleaf
Thymeleafの#datesユーティリティとは?初心者向け日付操作ガイド
更新記事
人気記事
No.1
Java&Spring記事人気No1
Java
Javaのクラスとインスタンス化、コンストラクタの使い方完全ガイド!初心者でもわかるオブジェクト指向の基礎
No.2
Java&Spring記事人気No2
JSP
JSPの基本タグ一覧と使い方まとめ!実務で使えるタグを紹介
No.3
Java&Spring記事人気No3
Spring
Spring BootとJavaの互換性一覧!3.5/3.4/3.3はJava 21・17に対応してる?
No.4
Java&Spring記事人気No4
Java
JavaのDateクラスの使い方を完全ガイド!初心者でもわかる日付操作
No.5
Java&Spring記事人気No5
Java
JavaのExceptionクラスを完全解説!初心者でも理解できる例外処理の基本
No.6
Java&Spring記事人気No6
Java
Java開発環境「Eclipse(Pleiades)」のインストール方法とメリットを初心者向けに解説
No.7
Java&Spring記事人気No7
Thymeleaf
Thymeleafのth:hrefの使い方を全解説。初心者でも完璧に理解できるガイド
No.8
Java&Spring記事人気No8
Thymeleaf
Thymeleafのth:valueの使い方を解説!フォーム入力値を保持したり動的に表示する方法