String.join() 用分隔符拼接字符串

方法定义

String.join() 是 Java 8 引入的静态方法,用于将多个字符串用指定的分隔符连接成一个字符串。

// 1. 用分隔符连接字符串数组
public static String join(CharSequence delimiter, CharSequence... elements)

// 2. 用分隔符连接 Iterable 中的元素
public static String join(CharSequence delimiter, Iterable<? extends CharSequence> elements)

参数说明

  • delimiter:分隔符,用于连接各个元素的字符序列
  • elements:要连接的字符串元素(可变参数或 Iterable 集合)

返回值

  • 连接后的字符串
  • 如果元素为空或长度为 0,返回空字符串

功能说明

String.join() 提供了一种简洁、高效的方式来将多个字符串用指定分隔符连接。相比传统的字符串拼接方式,它具有以下优势:

  • 简洁性:一行代码完成连接操作
  • 效率:内部使用 StringBuilder 优化性能
  • 可读性:代码意图清晰明确
  • 灵活性:支持数组和集合两种输入形式

示例代码

基本用法

public class JoinExample {
    public static void main(String[] args) {
        // 1. 连接字符串数组
        String[] fruits = {"apple", "banana", "orange", "grape"};
        String result1 = String.join(", ", fruits);
        System.out.println(result1);
        // 输出: apple, banana, orange, grape
        
        // 2. 连接 List 集合
        List<String> colors = Arrays.asList("red", "green", "blue");
        String result2 = String.join(" | ", colors);
        System.out.println(result2);
        // 输出: red | green | blue
        
        // 3. 使用不同分隔符
        String[] words = {"Hello", "world", "Java"};
        String result3 = String.join("-", words);
        System.out.println(result3);
        // 输出: Hello-world-Java
    }
}

实际应用场景

// 1. 构建 SQL IN 子句
public static String buildInClause(List<String> values) {
    return String.join(",", values.stream()
        .map(v -> "'" + v + "'")
        .toArray(String[]::new));
}

// 使用示例
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
String inClause = buildInClause(names);
System.out.println("IN (" + inClause + ")");
// 输出: IN ('Alice','Bob','Charlie')

// 2. 路径拼接
public static String buildPath(String... parts) {
    return String.join("/", parts);
}

// 使用示例
String path = buildPath("home", "user", "documents", "file.txt");
System.out.println(path);
// 输出: home/user/documents/file.txt

// 3. CSV 行构建
public static String buildCsvRow(List<String> fields) {
    return String.join(",", fields);
}

// 使用示例
List<String> row = Arrays.asList("John", "Doe", "30", "Engineer");
String csvRow = buildCsvRow(row);
System.out.println(csvRow);
// 输出: John,Doe,30,Engineer

特殊情况处理

// 1. 空数组或集合
String[] emptyArray = {};
List<String> emptyList = new ArrayList<>();
System.out.println(String.join(",", emptyArray)); // 输出空字符串
System.out.println(String.join(",", emptyList));  // 输出空字符串

// 2. 包含空字符串的元素
String[] withEmpty = {"a", "", "c", "d"};
System.out.println(String.join("-", withEmpty));
// 输出: a--c-d

// 3. 单个元素
String[] single = {"only"};
System.out.println(String.join(",", single));
// 输出: only (没有分隔符)

// 4. null 元素(会抛出 NullPointerException)
// String[] withNull = {"a", null, "c"};
// String.join(",", withNull); // 会抛出异常

使用技巧

1. 与 Stream 配合使用

// 处理数据并连接
List<String> names = Arrays.asList("alice", "bob", "charlie");
String result = names.stream()
    .map(String::toUpperCase)
    .collect(Collectors.joining(", "));
System.out.println(result);
// 输出: ALICE, BOB, CHARLIE

// 使用 Collectors.joining() 的更多选项
String detailed = names.stream()
    .map(String::toUpperCase)
    .collect(Collectors.joining(", ", "[", "]"));
System.out.println(detailed);
// 输出: [ALICE, BOB, CHARLIE]

2. 处理不同数据类型

// 连接数字
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
String numberStr = numbers.stream()
    .map(String::valueOf)
    .collect(Collectors.joining("-"));
System.out.println(numberStr);
// 输出: 1-2-3-4-5

// 连接对象
List<Person> people = Arrays.asList(
    new Person("Alice", 25),
    new Person("Bob", 30)
);
String peopleStr = people.stream()
    .map(Person::getName)
    .collect(Collectors.joining(", "));
System.out.println(peopleStr);
// 输出: Alice, Bob

3. 构建复杂字符串

// 构建查询参数
Map<String, String> params = new HashMap<>();
params.put("name", "john");
params.put("age", "30");
params.put("city", "ny");

String queryString = params.entrySet().stream()
    .map(entry -> entry.getKey() + "=" + entry.getValue())
    .collect(Collectors.joining("&"));

System.out.println(queryString);
// 输出: name=john&age=30&city=ny

常见错误

1. 忽略 null 值

// 错误:直接包含 null 值
// String[] withNull = {"a", null, "c"};
// String.join(",", withNull); // NullPointerException

// 正确:过滤 null 值
String[] withNull = {"a", null, "c"};
String result = Arrays.stream(withNull)
    .filter(Objects::nonNull)
    .collect(Collectors.joining(","));
System.out.println(result); // a,c

2. 分隔符选择错误

// 错误:使用了不需要的分隔符
String[] parts = {"file", "txt"};
String wrong = String.join("", parts); // filetxt (缺少点号)

// 正确:使用正确的分隔符
String correct = String.join(".", parts); // file.txt

3. 性能误区

// 不推荐:在循环中多次调用 join
List<String> results = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
    String[] data = getData(i);
    results.add(String.join(",", data)); // 每次创建新字符串
}

// 推荐:批量处理或使用 StringBuilder

注意事项

  1. null 安全性String.join() 本身不处理 null 元素,会抛出 NullPointerException
  2. 性能考虑:对于大量数据的连接,考虑使用 StringBuilderStringBuffer
  3. 内存使用:大字符串连接会产生大量临时对象
  4. 编码问题:确保所有字符串使用相同的字符编码
  5. 线程安全String.join() 是线程安全的,但结果字符串的使用需要注意

最佳实践

1. 创建工具方法

public class StringUtils {
    // 安全的 join 方法,过滤 null 值
    public static String safeJoin(CharSequence delimiter, String... elements) {
        if (elements == null || elements.length == 0) {
            return "";
        }
        
        return Arrays.stream(elements)
            .filter(Objects::nonNull)
            .collect(Collectors.joining(delimiter));
    }
    
    // 连接路径部分
    public static String joinPath(String... parts) {
        return Arrays.stream(parts)
            .filter(Objects::nonNull)
            .filter(s -> !s.isEmpty())
            .collect(Collectors.joining("/"));
    }
}

2. 处理复杂数据结构

// 连接嵌套集合
public static String joinNested(List<List<String>> nested) {
    return nested.stream()
        .map(list -> String.join("-", list))
        .collect(Collectors.joining("|"));
}

// 使用示例
List<List<String>> data = Arrays.asList(
    Arrays.asList("a", "b"),
    Arrays.asList("c", "d")
);
System.out.println(joinNested(data)); // a-b|c-d

3. 性能优化

// 对于大量数据的连接
public static String joinLargeDataset(List<String> data, String delimiter) {
    // 预估 StringBuilder 容量
    int estimatedLength = data.stream()
        .mapToInt(String::length)
        .sum() + (data.size() - 1) * delimiter.length();
    
    StringBuilder sb = new StringBuilder(Math.max(16, estimatedLength));
    
    for (int i = 0; i < data.size(); i++) {
        if (i > 0) {
            sb.append(delimiter);
        }
        sb.append(data.get(i));
    }
    
    return sb.toString();
}

4. 验证输入

public static String validatedJoin(String delimiter, String... elements) {
    if (delimiter == null) {
        throw new IllegalArgumentException("Delimiter cannot be null");
    }
    if (elements == null) {
        return "";
    }
    
    return Arrays.stream(elements)
        .filter(Objects::nonNull)
        .collect(Collectors.joining(delimiter));
}

性能优化

1. 预估容量

// 对于已知大小的数据集
public static String joinWithCapacity(List<String> items, String delimiter) {
    if (items.isEmpty()) return "";
    
    // 预估最终字符串长度
    int totalLength = items.stream()
        .mapToInt(String::length)
        .sum() + (items.size() - 1) * delimiter.length();
    
    StringBuilder sb = new StringBuilder(Math.max(16, totalLength));
    
    for (int i = 0; i < items.size(); i++) {
        if (i > 0) sb.append(delimiter);
        sb.append(items.get(i));
    }
    
    return sb.toString();
}

2. 批量处理

// 对于多个连接操作
public static List<String> batchJoin(List<List<String>> datasets, String delimiter) {
    return datasets.parallelStream() // 使用并行流
        .map(list -> String.join(delimiter, list))
        .collect(Collectors.toList());
}

3. 缓存常用结果

// 对于频繁使用的连接结果
private static final Map<List<String>, String> JOIN_CACHE = new ConcurrentHashMap<>();

public static String cachedJoin(String delimiter, List<String> elements) {
    return JOIN_CACHE.computeIfAbsent(elements, 
        list -> String.join(delimiter, list));
}

总结

String.join() 是 Java 中简洁高效的字符串连接工具,关键要点:

  1. 核心功能:用指定分隔符连接多个字符串
  2. 简洁性:相比传统拼接方式更简洁易读
  3. 灵活性:支持数组和集合两种输入形式
  4. 性能:内部使用 StringBuilder 优化
  5. 最佳实践
    • 处理 null 值和边界情况
    • 对于复杂需求结合 Stream 使用
    • 考虑性能优化和内存使用
    • 创建工具方法提高代码复用性

掌握 String.join() 方法能有效简化字符串连接操作,特别是在构建 SQL、路径、CSV 等格式化字符串时非常有用。