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
}
}
Javajoining() を引数なしで呼ぶと、
「区切り文字なしで、全部ただくっつける」動きになります。
ここでやっていることを日本語で言うと、
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
}
}
JavaCollectors.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; }
}
JavaList<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
Javamap で「 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 を使うときに意識しておきたいこと
どこで「終端」にするか
joining は collect の中で使われる 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 行で持つ」といった高度な集計も簡単に書ける
