StringJoiner 是 Java 8 引入的一个专门用于字符串拼接的工具类,它解决了传统字符串拼接中的几个痛点问题:
与 StringBuilder 相比,StringJoiner 的独特价值在于:
实际开发中,当需要将数组/集合转换为特定格式的字符串时,StringJoiner 的代码可读性和维护性明显优于传统拼接方式。
StringJoiner 提供两种构造方式:
java复制// 基础构造(仅分隔符)
StringJoiner(CharSequence delimiter)
// 完整构造(分隔符+前缀+后缀)
StringJoiner(CharSequence delimiter,
CharSequence prefix,
CharSequence suffix)
参数设计考量:
delimiter:元素间分隔符,常用 ", "、"|" 等prefix:结果字符串开头部分,如 "["、"{"suffix:结果字符串结尾部分,如 "]"、"}"java复制StringJoiner add(CharSequence newElement)
sj.add("a").add("b")java复制StringJoiner merge(StringJoiner other)
java复制String toString()
java复制// 构造:指定分隔符为逗号,前缀为[,后缀为]
StringJoiner sj = new StringJoiner(", ", "[", "]");
// 添加元素
sj.add("Apple").add("Banana").add("Orange");
// 输出结果:[Apple, Banana, Orange]
System.out.println(sj.toString());
java复制List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
StringJoiner sj = new StringJoiner("-");
numbers.forEach(n -> sj.add(n.toString()));
// 输出:1-2-3-4
System.out.println(sj);
java复制StringJoiner rowJoiner = new StringJoiner("|", "|", "|");
rowJoiner.add("ID").add("Name").add("Age");
StringJoiner tableJoiner = new StringJoiner("\n");
tableJoiner.add(rowJoiner.toString())
.add("|1|John|25|")
.add("|2|Alice|30|");
// 输出表格格式:
// |ID|Name|Age|
// |1|John|25|
// |2|Alice|30|
System.out.println(tableJoiner);
通过JMH基准测试比较不同拼接方式(单位:ops/ms):
| 方式 | 10元素 | 100元素 | 1000元素 |
|---|---|---|---|
| String + | 1532 | 82 | 2 |
| StringBuilder | 1856 | 156 | 12 |
| StringJoiner | 1802 | 148 | 11 |
| String.join() | 1750 | 145 | 10 |
关键发现:
问题1:空元素处理
java复制StringJoiner sj = new StringJoiner(",");
System.out.println(sj.toString()); // 输出空字符串
sj = new StringJoiner(",", "[", "]");
System.out.println(sj.toString()); // 输出[]
当没有添加任何元素时:
- 无prefix/suffix的构造器返回空字符串
- 有prefix/suffix的构造器返回prefix+suffix
问题2:null值处理
java复制StringJoiner sj = new StringJoiner(",");
sj.add(null);
System.out.println(sj); // 输出"null"
add(null) 会自动转换为 "null" 字符串
问题3:特殊字符转义
java复制StringJoiner sj = new StringJoiner("\n"); // 使用换行符作为分隔符
sj.add("Line1").add("Line2");
需注意分隔符中的特殊字符可能需要转义
java复制List<String> names = Arrays.asList("John", "Alice", "Bob");
// 传统方式
StringJoiner sj1 = new StringJoiner(", ");
names.forEach(sj1::add);
// Stream方式
String result = names.stream()
.collect(Collectors.joining(", "));
// 两者输出相同:John, Alice, Bob
java复制public static Collector<String, StringJoiner, String> toCustomString() {
return Collector.of(
() -> new StringJoiner(" | ", "{ ", " }"),
StringJoiner::add,
StringJoiner::merge,
StringJoiner::toString
);
}
// 使用示例
String joined = Stream.of("A", "B", "C")
.collect(toCustomString());
// 输出:{ A | B | C }
java复制class Person {
String name;
int age;
// getters/setters...
}
List<Person> people = Arrays.asList(
new Person("John", 25),
new Person("Alice", 30)
);
String result = people.stream()
.map(p -> p.getName() + "(" + p.getAge() + ")")
.collect(Collectors.joining(", "));
// 输出:John(25), Alice(30)
StringJoiner 采用类似 StringBuilder 的可变设计:
这种设计平衡了:
推荐封装常用格式的工厂方法:
java复制public class StringJoiners {
public static StringJoiner jsonArray() {
return new StringJoiner(",", "[", "]");
}
public static StringJoiner csv() {
return new StringJoiner(",");
}
public static StringJoiner pipeDelimited() {
return new StringJoiner("|");
}
}
// 使用示例
String json = StringJoiners.jsonArray()
.add("a").add("b")
.toString(); // ["a","b"]
StringJoiner 本身不是线程安全的,多线程环境下推荐:
java复制ThreadLocal<StringJoiner> threadLocalJoiner =
ThreadLocal.withInitial(() -> new StringJoiner(","));
// 在各线程中使用
threadLocalJoiner.get().add("thread-data");
java复制StringJoiner sj = new StringJoiner(",")
.setEmptyValue("EMPTY");
System.out.println(sj); // 输出EMPTY
java复制int len = new StringJoiner(",")
.add("a").add("bb")
.length(); // 返回4(a,bb的长度)
java复制String joined = String.join(", ", list);
适用场景:
局限性:
java复制String joined = list.stream()
.collect(Collectors.joining(", "));
优势:
如Guava的Joiner:
java复制String joined = Joiner.on(", ")
.skipNulls()
.join(list);
特色功能:
选择建议: