一、什么是StringJoiner
StringJoiner是Java 8新增的一个API,他是基于StringBuilder实现,用于实现对字符串之间通过分隔符拼接的场景。
有些字符串拼接场景,使用StringBuilder或StringBuffer会显得比较繁琐。
如以下字符串:
(hello, guys, 欢迎大家)
这种字符串有前缀后缀并且由 “,” 分隔的字符串,在 Java 8 之前要使用 StringBuilder/ StringBuffer 进行拼接,如下:
StringBuilder sb = new StringBuilder();
sb.append("(");
sb.append("hello");
sb.append(",");
sb.append("guys");
sb.append(",");
sb.append("欢迎大家");
sb.append(")");
String str = sb.toString();
上述代码显得十分难看又略显繁琐。
二、StringJoiner基本使用
(hello, guys, 欢迎大家)
Java 8使用StringJonier来实现,如下:
StringJoiner stringJoiner = new StringJoiner(",", "(", ")");
stringJoiner.add("hello");
stringJoiner.add("guys");
stringJoiner.add("欢迎大家");
StringJonier实现就显得比较优雅!
三、StringJoiner详细介绍
StringJoiner 的类结构图:
3.1、成员变量
- prefix:拼接后的字符串前缀
- delimiter:拼接时的字符串分隔符
- suffix:拼接后的字符串后缀
- value:拼接后的值
- emptyValue:空值的情况,
value为 null 时返回
3.2、构造方法
StringJoiner有两个构造方法
一个是要求传入分隔符、前缀和后缀
另一个是只需要传入分隔符(前缀和后缀默认是"")
源码如下:
public StringJoiner(CharSequence delimiter) {
this(delimiter, "", "");
}
public StringJoiner(CharSequence delimiter,
CharSequence prefix,
CharSequence suffix) {
Objects.requireNonNull(prefix, "The prefix must not be null");
Objects.requireNonNull(delimiter, "The delimiter must not be null");
Objects.requireNonNull(suffix, "The suffix must not be null");
// make defensive copies of arguments
this.prefix = prefix.toString();
this.delimiter = delimiter.toString();
this.suffix = suffix.toString();
this.emptyValue = this.prefix + this.suffix;
}
可以看到 emptyValue
默认为前缀+后缀组成。
3.3、公开方法
- setEmptyValue:设置空值
- toString:转换成 String
- add:添加字符串
- merge:从另一个 StringJoiner 合并
- length:长度(包括前缀后缀)
主要看add()源码:
public StringJoiner add(CharSequence newElement) {
prepareBuilder().append(newElement);
return this;
}
private StringBuilder prepareBuilder() {
if (value != null) {
value.append(delimiter);
} else {
value = new StringBuilder().append(prefix);
}
return value;
}
toString()源码
public String toString() {
if (value == null) {
return emptyValue;
} else {
if (suffix.equals("")) {
return value.toString();
} else {
int initialLength = value.length();
String result = value.append(suffix).toString();
// reset value to pre-append initialLength
value.setLength(initialLength);
return result;
}
}
}
可以看到内部其实就是用的StringBuilder进行封装的,首次创建会先拼接前缀,后续先添加分隔符,再添加字符串。
主要就通过上述两个方法,实现前缀、后缀以及分隔符的拼接。(最后输出才进行后缀的拼接)
另外一点,add 方法就是返回 StringJoiner 本身,所以可以像StringBuilder/ StringBuffer 一样进行流式处理。如下:
StringJoiner stringJoiner = new StringJoiner(",", "(", ")").add("hello").add("guys").add("欢迎大家");
System.out.println(stringJoiner);
3.4、空值处理
没有拼接任何字符串的几个空值处理场景。
3.4.1、输出空白字符串
StringJoiner stringJoiner = new StringJoiner(",");
System.out.println(stringJoiner.toString());
3.4.2、输出前后缀
StringJoiner stringJoiner = new StringJoiner(",", "[", "]");
System.out.println(stringJoiner.toString());
输出:[]
3.4.3、输出指定字符串
StringJoiner stringJoiner = new StringJoiner(",", "[", "]");
stringJoiner.setEmptyValue("likelong");
System.out.println(stringJoiner.toString());
输出:likelong
上面三个小案例都可以在源码中找到答案。
四、String.join()
String.join() 这是针对 StringJoiner 又封装了一层的 API,同样出自 Java 8,可以传入动态参数或者迭代器。
- java.lang.String#join(java.lang.CharSequence, java.lang.CharSequence…)
- java.lang.String#join(java.lang.CharSequence, java.lang.Iterable<? extends java.lang.CharSequence>)
源码:
public static String join(CharSequence delimiter, CharSequence... elements) {
Objects.requireNonNull(delimiter);
Objects.requireNonNull(elements);
// Number of elements not likely worth Arrays.stream overhead.
StringJoiner joiner = new StringJoiner(delimiter);
for (CharSequence cs: elements) {
joiner.add(cs);
}
return joiner.toString();
}
public static String join(CharSequence delimiter,
Iterable<? extends CharSequence> elements) {
Objects.requireNonNull(delimiter);
Objects.requireNonNull(elements);
StringJoiner joiner = new StringJoiner(delimiter);
for (CharSequence cs: elements) {
joiner.add(cs);
}
return joiner.toString();
}
由源码可以看出,该方法只能进行分隔符拼接,并不能拼接前缀和后缀。
实际操作:
String join = String.join(",", "hello", "world", "likelong");
System.out.println(join);
输出:hello,world,likelong
五、Collectors.joining(“,”)
Collectors.joining(“,”)底层也是通过StringJoiner实现的。源码如下:
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter) {
return joining(delimiter, "", "");
}
/**
* Returns a {@code Collector} that concatenates the input elements,
* separated by the specified delimiter, with the specified prefix and
* suffix, in encounter order.
*
* @param delimiter the delimiter to be used between each element
* @param prefix the sequence of characters to be used at the beginning
* of the joined result
* @param suffix the sequence of characters to be used at the end
* of the joined result
* @return A {@code Collector} which concatenates CharSequence elements,
* separated by the specified delimiter, in encounter order
*/
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,
CharSequence prefix,
CharSequence suffix) {
return new CollectorImpl<>(
() -> new StringJoiner(delimiter, prefix, suffix),
StringJoiner::add, StringJoiner::merge,
StringJoiner::toString, CH_NOID);
}
针对不同的场景使用不同的 API,这才是最佳最优雅的处理方式,不要只会使用 StringBuilder!