コース一覧
    Java中級:Streamと関数型
    distinct で重複排除

    Java中級:Streamと関数型

    Stream APIと関数型インターフェースを学び、データ処理を宣言的で再利用しやすい形に書けるようにします。

    1
    Stream API
    01. Stream を作る5分
    02. filter で絞り込む5分
    03. map で変換する5分
    04. reduce で集約する5分
    05. collect で集める5分
    06. sorted で並び替え5分
    07. distinct で重複排除5分
    08. limit と skip5分
    09. 並列ストリーム5分
    10. Stream API まとめクイズ5分
    2
    関数型インターフェース
    01. ラムダ式の基本5分
    02. メソッド参照 ::5分
    03. Function インターフェース5分
    04. Predicate インターフェース5分
    05. Consumer と Supplier5分
    06. BiFunction と 2 引数の関数5分
    07. 関数型インターフェース まとめクイズ5分

    distinct で重複排除

    同じ人を 3 回数えてしまう

    アクセスログのユーザー ID を並べたリストから「何人が来たか」を出したいとします。行数をそのまま数えると、答えがずれます。

    Java

    List<String> ids = List.of("u1", "u2", "u1", "u3", "u1");
    System.out.println(ids.size()); // 5

    来たのは 3 人ですが、u1 が 3 回訪れているので 5 と出ます。自力で数えようとすると、すでに見た ID を覚えておく Set を用意して、for の中で「入っていなければ足す」を書くことになります。ここまで来ると、やりたいことより準備の方が長くなります。

    distinct() は、この「すでに見たかどうか」を代わりに覚えてくれる中間操作です。

    Java

    List<String> unique = ids.stream().distinct().toList();
    // [u1, u2, u3]

    最初に出てきた 1 つを残し、後から来た同じものを捨てます。残った要素の順番は、元の並びのままです。

    同じかどうかは equals と hashCode で決まる

    String や Integer なら、distinct() は素直に効きます。困るのは自作クラスです。

    Java

    List<User> users = List.of(new User("u1"), new User("u1"));
    System.out.println(users.stream().distinct().toList()); // 2 件とも残る

    id はどちらも u1 なのに、2 件とも残ります。distinct() は中身を見比べているのではなく、要素に equals と hashCode を尋ねているからです。何も書いていないクラスは、この 2 つを Object から受け継いだままで、その中身は「同じ場所にあるオブジェクトだけを同じとみなす」というものです。new を 2 回すれば別の場所にできるので、別物と判定されます。

    自作クラスで中身の一致を見てほしいなら、両方を自分で書きます。

    Java

    class User {
        String id;
        User(String id) { this.id = id; }
    
        @Override
        public boolean equals(Object o) {
            if (!(o instanceof User)) return false;
            return id.equals(((User) o).id);
        }
    
        @Override
        public int hashCode() {
            return id.hashCode();
        }
    }

    hashCode を書き忘れると、equals だけでは重複が残ります。distinct() は速く探すために先に hashCode で振り分けてから equals を呼ぶので、hashCode がばらばらだと、equals を呼ぶところまで辿り着けないからです。この 2 つは必ずセットで書きます。

    解説

    IDE には equals と hashCode をまとめて生成する機能があります。片方だけ手書きするより、生成させて必要なフィールドだけ選ぶ方が確実です。

    やってみよう

    Solution.uniqueCount(int[] arr) を完成させて、配列に何種類の値が入っているかを返してください。

    ファイルの先頭に import java.util.stream.IntStream; を書き、IntStream.of(arr) から Stream を作ります。distinct() を通してから、要素の個数を数えてください。今回扱うのは int なので、equals や hashCode を自分で書く必要はありません。

    数えた結果は long で返ってくるので、(int) にキャストしてから return します。空配列を渡したときは、通る要素が 1 つも無いので 0 になります。

    要件

    1. ファイル先頭で import java.util.stream.IntStream; を書くこと
    2. IntStream.of(arr) から Stream を作り、distinct() を必ず使うこと
    3. count() の戻り値 (long) を int にキャストして return すること

    ヒント

    `IntStream.of(arr).distinct().count()` で重複を除いた要素数 (`long`) が取れます

    `count()` は `long` を返すので、戻り値の型 `int` に合わせて `(int)` キャストしましょう

    空配列でも `count()` が `0L` を返してくれるので、`if` で分岐する必要はありません

    生田 陸人
    監修生田 陸人
    ゆめさくエンジニア / 現役ソフトウェアエンジニア監修者プロフィールを見る →
    編集 ゆめさく編集部·公開 2026/05/19·更新 2026/08/26

    関連レッスン

    • limit と skip

      Stream API の中間操作 limit と skip を使い、要素を先頭から数件だけ取り出したり、先頭の数件を飛ばしたりする方法を学んで、配列のスライス処理を Stream で書いてみよう。

    • 並列ストリーム

      Stream API の parallel() で配列を複数 CPU コアに分割集計させる並列ストリームの仕組みと、結合性・スレッド安全・順序保証など並列化の落とし穴を学びます。

    • Stream API まとめクイズ

      Stream API 章で学んだ生成 / filter / map / reduce / collect / sorted / distinct / limit / skip / parallel を、メソッドチェーンの順序と中間操作・ターミナル操作の使い分けで総ざらいする 4 択クイズ。

    • ラムダ式の基本

      Java 8 から導入されたラムダ式の文法と関数型インターフェースの考え方を学び、Stream と組み合わせて配列の合計を一行で書く感覚を身につけよう。

    このレッスンに出てくる用語

    意味があいまいなまま進んだ語は、ここから読み直せます。

    • リスト順序付きで複数の値を扱うデータ構造
    • クラスデータと振る舞いを束ねる単位
    • オブジェクトキーと値のペアで構造を作るデータ
    • セット重複を持たない順序なし集合
    • フィールドクラスが持つデータ
    • 配列サイズ固定の同型データの集まり
    • instanceof「このオブジェクトの実際の型は何か」を確認 → 安全に変換
    • 戻り値呼び出し元への返答を表す点線矢印
    main.java
    エディタを読み込んでいます

    メモ

    distinct で重複排除

    ⌘S で保存