Java Tips | コレクション:join処理

Java Java
スポンサーリンク

join処理は「バラバラの要素を“1本の文字列”にまとめる」技

業務システムでは、
「IDをカンマ区切りでログに出したい」
「SQLの IN ('A','B','C') を組み立てたい」
「画面に 山田 / 佐藤 / 鈴木 のように表示したい」

こういう「複数要素を、区切り文字付きの1本の文字列にしたい」場面が本当に多いです。
この「要素をつなげて1本にする」処理が、ここでいう join 処理です。

Java には String.joinCollectors.joining など、join 専用の仕組みが用意されています。
これをうまくユーティリティ化しておくと、業務コードがかなりスッキリします。


一番基本:String.join で List をつなげる

文字列の List を区切り文字で join する

まずは、List<String> をカンマ区切りにする基本形です。

import java.util.List;

public class JoinBasic {

    public static void main(String[] args) {
        List<String> names = List.of("山田", "佐藤", "鈴木");

        String joined = String.join(",", names);

        System.out.println(joined); // 山田,佐藤,鈴木
    }
}
Java

ここでの重要ポイントは二つです。

一つ目は、「String.join(区切り文字, Iterable<String>) という形で、“区切り文字”と“文字列の集まり”を渡すだけでよい」ことです。
自分で StringBuilder を回したり、最後の区切りを消したりする必要はありません。

二つ目は、「要素が0件なら空文字列、1件ならそのまま、2件以上なら区切り文字付きでつながる」という挙動が、すべて String.join に任せられることです。
“最後に余計なカンマが付く”といったありがちなバグを、最初から避けられます。


Stream と Collectors.joining の組み合わせ

変換しながら join したいとき

「そのままの文字列をつなげる」のではなく、
「何かに変換してから join したい」こともよくあります。

例えば、「数値の List をカンマ区切り文字列にしたい」場合。

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

public class JoinWithStream {

    public static void main(String[] args) {
        List<Integer> scores = List.of(80, 90, 75);

        String joined =
                scores.stream()
                      .map(String::valueOf)
                      .collect(Collectors.joining(","));

        System.out.println(joined); // 80,90,75
    }
}
Java

ここでの重要ポイントは三つです。

一つ目は、「Collectors.joining(区切り文字) が、“Stream<String> を1本の文字列にまとめる Collector”である」ことです。
map(String::valueOf)Stream<String> に変換してから、joining でつなげています。

二つ目は、「joiningprefix(先頭に付ける文字)と suffix(末尾に付ける文字)も指定できる」ことです。

String joined =
        scores.stream()
              .map(String::valueOf)
              .collect(Collectors.joining(",", "[", "]"));
// 結果: [80,90,75]
Java

三つ目は、「Stream の中でフィルタやソートをしながら join できる」ことです。

String joined =
        scores.stream()
              .filter(s -> s >= 80)
              .sorted()
              .map(String::valueOf)
              .collect(Collectors.joining(","));
// 80,90
Java

「加工しながら join する」というパターンは、業務でかなり頻出します。


SQL IN 句など「囲み文字付き」の join

'A','B','C' のような形を作る

SQL の IN 句などでは、
IN ('A','B','C') のように、要素をシングルクォートで囲んでカンマ区切りにしたいことが多いです。

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

public class JoinForSql {

    public static void main(String[] args) {
        List<String> ids = List.of("u001", "u002", "u003");

        String inValues =
                ids.stream()
                   .map(id -> "'" + id + "'")
                   .collect(Collectors.joining(","));

        String sql = "SELECT * FROM user WHERE id IN (" + inValues + ")";

        System.out.println(sql);
        // SELECT * FROM user WHERE id IN ('u001','u002','u003')
    }
}
Java

ここでの重要ポイントは二つです。

一つ目は、「map で“囲み文字付きの文字列”に変換してから join している」ことです。
Collectors.joining 自体は「区切り」「前後の文字」は扱えますが、「各要素をどう加工するか」は map でやるのが基本です。

二つ目は、「SQL 組み立てでは、本来はプレースホルダ(?)+バインド変数を使うべき」という前提を忘れないことです。
ここでは join の例として書いていますが、実務では SQL インジェクション対策を必ず意識してください。


null や空要素をどう扱うかを決める

null を飛ばすか、そのまま "null" にするか

join 処理で地味にハマるのが、「null や空文字が混ざっている場合」です。

例えば、List<String> names = Arrays.asList("山田", null, "鈴木"); のようなケース。
String.join(",", names) を呼ぶと、NullPointerException になります。

「null は飛ばしたい」なら、Stream でフィルタしてから join します。

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

List<String> names = Arrays.asList("山田", null, "鈴木");

String joined =
        names.stream()
             .filter(Objects::nonNull)
             .collect(Collectors.joining(","));
// 山田,鈴木
Java

「null も "null" として出したい」なら、String.valueOf を使う手もあります。

String joined =
        names.stream()
             .map(String::valueOf) // null → "null"
             .collect(Collectors.joining(","));
// 山田,null,鈴木
Java

ここでの重要ポイントは、
「null や空文字をどう扱うかを、join ユーティリティ側で決めておくと、呼び出し側が楽になる」ということです。


join 専用ユーティリティを作る

「毎回同じパターン」を名前付きにする

業務でよく使う join パターンは、ユーティリティにまとめてしまうと読みやすくなります。

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

public final class Joins {

    private Joins() {}

    public static String joinComma(Collection<String> values) {
        if (values == null || values.isEmpty()) {
            return "";
        }
        return values.stream()
                     .filter(Objects::nonNull)
                     .collect(Collectors.joining(","));
    }

    public static String joinWithQuotes(Collection<String> values) {
        if (values == null || values.isEmpty()) {
            return "";
        }
        return values.stream()
                     .filter(Objects::nonNull)
                     .map(v -> "'" + v + "'")
                     .collect(Collectors.joining(","));
    }
}
Java

使い方のイメージです。

String csv = Joins.joinComma(names);
String inValues = Joins.joinWithQuotes(ids);
Java

ここでの重要ポイントは三つです。

一つ目は、「null や空コレクションの扱いをユーティリティ側で統一している」ことです。
呼び出し側は「とりあえず join したい」とだけ書けばよく、毎回 null チェックを書かなくて済みます。

二つ目は、「joinCommajoinWithQuotes という名前で、“どう join するか”が一目で分かる」ことです。
Collectors.joining(",") があちこちに散らばるより、意図が読みやすくなります。

三つ目は、「業務でよく使うパターン(CSV、SQL IN 句など)を“レシピ化”しておく」ことです。
毎回同じ書き方をコピペするより、ユーティリティに寄せた方が安全でメンテしやすくなります。


まとめ:join処理ユーティリティで身につけてほしい感覚

join処理は、
単に「文字列をつなげる」話ではなく、
「バラバラの要素を、人間や外部システムが読みやすい形に整える技術」です。

String.join で「そのままの文字列の集まり」を簡単につなげる。
Collectors.joining と Stream を組み合わせて、「変換しながら join する」パターンを身につける。
null や空要素、囲み文字(クォート)などの扱いを、ユーティリティとして決めておく。
CSV、ログ、SQL など、「文字列の列挙」が必要な場面で、毎回同じ join パターンを再利用できるようにする。

あなたのコードのどこかに、
StringBuilder で for 文を回しながら「最後のカンマを消す」ような処理があれば、
それを一度「join ユーティリティ」に置き換えられないか眺めてみてください。

その小さな置き換えが、
「文字列の列挙を、きれいで安全な形で扱えるエンジニア」への、
確かな一歩になります。

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