Java | Java 標準ライブラリ:joining

Java Java
スポンサーリンク

joining をざっくり一言でいうと

Collectors.joining は、

「Stream の中の文字列たちを、1 本の文字列に“つなげて”まとめるための Collector

です。

collect(Collectors.joining("区切り")) と書くことで、

"Alice", "Bob", "Carol""Alice,Bob,Carol"

のように、「間に区切りを入れながら 1 本の String にする」ことができます。

collect が「どう集めるか?」を指定する場所で、
joining は「文字列として 1 本に連結してくれ」という“まとめ方テンプレ”になっています。


まずは基本:文字列の List を 1 本の文字列にする

区切りなしで全部くっつける

一番シンプルな形から見てみます。

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

public class JoiningBasic {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Carol");

        String result =
                names.stream()
                     .collect(Collectors.joining());  // 区切りなし

        System.out.println(result);  // AliceBobCarol
    }
}
Java

joining() を引数なしで呼ぶと、
「区切り文字なしで、全部ただくっつける」動きになります。

ここでやっていることを日本語で言うと、

names.stream()Stream<String> を作る
collect(Collectors.joining()) で、「流れてきた文字列を全部くっつけて 1 本の String にする」

という処理です。

区切り文字を入れてつなげる(これが一番よく使う形)

現場で一番よく使うのは、区切り文字付きの形です。

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

public class JoiningWithDelimiter {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Carol");

        String csv =
                names.stream()
                     .collect(Collectors.joining(", "));

        System.out.println(csv);  // Alice, Bob, Carol
    }
}
Java

Collectors.joining(", ") と書くと、

最初の要素の前には何もつけない
各要素の“間”にだけ ", " を挟む

というルールで連結してくれます。

テーブル表示用の文字列
ログ出力用の 1 行
CSV や SQL IN 句((1,2,3) の中身)など

とにかく「複数の文字列を“区切り付きで一行にしたい」場面で、joining はとてもよく使います。


join のすごく地味だけど重要なポイント:自分でループを書かなくていい

for + StringBuilder で書くとどうなるか

同じことを、従来の for 文で書こうとするとこうなります。

List<String> names = Arrays.asList("Alice", "Bob", "Carol");

StringBuilder sb = new StringBuilder();
for (int i = 0; i < names.size(); i++) {
    if (i > 0) {
        sb.append(", ");
    }
    sb.append(names.get(i));
}
String result = sb.toString();
Java

やっていることは、

最初の要素の前には区切りをつけず
2 個目以降の要素の前にだけ区切りをつける

という処理ですが、自分で書くと if 文が必要になり、地味に間違えやすいです。

それを 1 行にまとめてくれるのが、

String result = names.stream()
                     .collect(Collectors.joining(", "));
Java

です。

「区切りの扱い」を全部 Collector 側に任せられるので、
自分は「何を区切りにするか」だけに集中できます。


joining のオーバーロードを整理する(区切り・前後の飾り)

3 パターンの形がある

Collectors.joining には、主に 3 つの形があります。

区切りなし:

Collectors.joining()
Java

区切りだけ指定:

Collectors.joining(CharSequence delimiter)
Java

区切り・前置文字(prefix)・後置文字(suffix)を指定:

Collectors.joining(CharSequence delimiter,
                   CharSequence prefix,
                   CharSequence suffix)
Java

それぞれ具体例で見ていきます。

delimiter だけ指定する形

さきほどの例がこれです。

String csv =
    names.stream()
         .collect(Collectors.joining(", "));
Java

", " が要素の間に入ります。

1 個だけならそのまま
2 個以上なら「要素, 要素, 要素…」

という形で連結されます。

prefix / suffix もつける形

例えば、「配列っぽい表示にしたい」とき。

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

public class JoiningWithPrefixSuffix {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Carol");

        String formatted =
                names.stream()
                     .collect(Collectors.joining(
                         ", ",   // 区切り
                         "[",    // 先頭につける文字
                         "]"     // 最後につける文字
                     ));

        System.out.println(formatted);  // [Alice, Bob, Carol]
    }
}
Java

これなら、自分で "[" + ... + "]" を組み立てる必要はありません。
「前にこれ、間にこれ、最後にこれ」と宣言するだけで、joining がきれいに仕上げてくれます。


joining と map の組み合わせ(非 String 型からの連結)

そのままだと String しか連結できない

Collectors.joining は、基本的に「String の Stream」に対して使う前提です。

例えば:

Stream<String> s = Stream.of("A", "B", "C");
String joined = s.collect(Collectors.joining(","));
Java

これは OK ですが、Stream<Integer> にはそのままでは使えません。

先に map で文字列に変換してから joining する

よくあるのが、「数値のリストをカンマ区切り文字列にしたい」パターンです。

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

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

        String csv =
                numbers.stream()
                       .map(n -> n.toString())        // まず String に変換
                       .collect(Collectors.joining(","));

        System.out.println(csv);  // 10,20,30
    }
}
Java

ここでは、

Stream<Integer>map(n -> n.toString())Stream<String>
joining(",")"10,20,30" に連結

という流れになっています。

自作クラスの特定フィールドを joining する

例えば User クラスがあって、「名前だけカンマ区切りで出したい」場合。

class User {
    String name;
    User(String name) { this.name = name; }
}
Java
List<User> users = List.of(new User("Alice"), new User("Bob"), new User("Carol"));

String names =
        users.stream()
             .map(u -> u.name)             // User -> String(名前を取り出す)
             .collect(Collectors.joining(", "));

System.out.println(names); // Alice, Bob, Carol
Java

map で「 joining しやすい形(文字列)」に変換してから、joining に渡す、というパターンはよく出てきます。


groupingBy と joining を組み合わせた例(少し応用)

例題:都道府県ごとに、ユーザー名をカンマ区切りにする

少しだけステップアップして、groupingBy と組み合わせた例を見ます。

import java.util.*;
import java.util.stream.Collectors;

class User {
    String name;
    String prefecture;

    User(String name, String prefecture) {
        this.name = name;
        this.prefecture = prefecture;
    }
}

public class GroupingAndJoining {
    public static void main(String[] args) {
        List<User> users = List.of(
            new User("Alice", "東京"),
            new User("Bob",   "大阪"),
            new User("Carol", "東京")
        );

        Map<String, String> namesByPref =
            users.stream()
                 .collect(Collectors.groupingBy(
                     u -> u.prefecture,                   // キー:都道府県
                     Collectors.mapping(
                         u -> u.name,                     // まず name を取り出して
                         Collectors.joining(", ")         // カンマ区切りにまとめる
                     )
                 ));

        System.out.println(namesByPref);
        // 例: {大阪=Bob, 東京=Alice, Carol}
    }
}
Java

ここで出てきた Collectors.mapping は少し応用ですが、
やっていることを分解すると次のとおりです。

都道府県ごとに group(groupingBy(u -> u.prefecture)
各グループの中身(User)から name だけを取り出す(mapping(u -> u.name, ...)
その名前たちを joining(", ") で 1 本の文字列にする

つまり、

東京 → “Alice, Carol”
大阪 → “Bob”

のような Map<String, String> を作っています。

「グループごとの一覧を、カンマ区切り文字列として持ちたい」
みたいなときに、groupingBy + mapping + joining は非常に強力です。


joining を使うときに意識しておきたいこと

どこで「終端」にするか

joiningcollect の中で使われる Collector なので、
これを使った時点で「Stream は終端になります」。

stream().filter(...).map(...).collect(joining(","))

という書き方は、
「ここでモノリスト(String)にまとめ切る」
という意味です。

このあと同じストリームに対して別の操作はできません。

「List に戻してから別処理をしたいのか」
「文字列にして終わりでいいのか」

この線引きを意識すると、コードの設計がスッキリします。

空の場合は空文字になること

対象のストリームが要素 0 個だった場合、

joining()
joining(",")

結果は「空文字列 ""」になります。

これは自然な挙動ですが、
「絶対に 1 文字以上欲しい」場合には、
呼び出し側で空チェックをしたり、orElse 的なものを使ったりする必要があります。


まとめ:joining を自分の中でどう位置づけるか

Collectors.joining を初心者向けに一言でまとめると、

Stream の文字列要素を、“区切り付き or なしで 1 本の文字列に連結するための Collector”

です。

意識しておきたいポイントは次のとおりです。

  • 区切りなし → joining()
  • 区切りだけ → joining(", ")
  • 区切り+前後の装飾 → joining(", ", "[", "]")
  • 非 String 型には、まず map で文字列に変換してから使う
  • groupingBy や mapping と組み合わせると、「グループごとの名前一覧を 1 行で持つ」といった高度な集計も簡単に書ける

タイトルとURLをコピーしました