Java工具类之StringJoiner源码分析
一、概述
StringJoiner
是java.util
包下的一个工具类,是JDK 1.8
引入的字符串拼接器。
作用是在构造字符串时,可以自动添加前缀、后缀及分隔符,而不需要自己去实现这些添加字符的逻辑。
虽然这也可以在StringBuilder
类的帮助下在每个字符串之后附加分隔符,但StringJoiner
提供了简单的方法来实现,而无需编写大量代码。
二、源码分析
2.1 属性
其实StringJoiner
主要是通过维护了一个StringBuilder
对象value
去添加元素的。
// 当前StringJoiner对象前缀
private final String prefix;
// 每个添加元素的分隔符
private final String delimiter;
// 当前StringJoiner对象后缀
private final String suffix;
// 前缀+元素+分隔符+后缀的值,如果没有添加元素,那么value是null
private StringBuilder value;
// 前缀+后缀的值,如果没有前后缀,那么这个值为空字符串,可以理解为value的副本,在value为null时,用它来代替
private String emptyValue;
2.2 构造方法
第一个构造方法是只传入分隔符,第二个构造方法是传入分隔符,还有前缀和后缀。
public StringJoiner(CharSequence delimiter) {
// 这里只是调用了第二个构造方法,前缀和后缀传入空字符串,表示没有前后缀
this(delimiter, "", "");
}
public StringJoiner(CharSequence delimiter,
CharSequence prefix,
CharSequence suffix) {
// 做了以下判断,如果分隔符,前后缀为null,则抛出NullPointerException异常
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");
// 赋值给当前对象的属性
this.prefix = prefix.toString();
this.delimiter = delimiter.toString();
this.suffix = suffix.toString();
this.emptyValue = this.prefix + this.suffix;
}
2.3 setEmptyValue方法
由于前文提到了emptyValue
,那么首先提供了一个setEmptyValue
方法:
public StringJoiner setEmptyValue(CharSequence emptyValue) {
this.emptyValue = Objects.requireNonNull(emptyValue,
"The empty value must not be null").toString();
return this;
}
在确定此StringJoiner
的字符串表示形式以及该字符串为空时,将使用这些字符。那将是没有添加任何元素。
2.4 add方法
添加一个元素,初始化value
的工作也是在这里做的,如果当前StringJoiner
没有调用过一次add
方法,那么value
为null
。
在调用add
方法时会自动调用,判断value
是否为null
,如果不为null
,直接添加分隔符。如果为null
,构造一个StringBuilder
对象,初始值为prefix
。
public StringJoiner add(CharSequence newElement) {
// 在此调用prepareBuilder方法,prepareBuilder会自动判断value是否已经初始化,并添加好分隔符
prepareBuilder().append(newElement);
return this;
}
private StringBuilder prepareBuilder() {
// 如果不为null,在添加元素前添加分隔符
if (value != null) {
value.append(delimiter);
} else {
// 反之,构建StringBuilder对象,初始值为prefix
value = new StringBuilder().append(prefix);
}
return value;
}
2.5 merge方法
合并一个StringJoiner
对象到当前StringJoiner
对象。
public StringJoiner merge(StringJoiner other) {
Objects.requireNonNull(other);
if (other.value != null) {
final int length = other.value.length();
StringBuilder builder = prepareBuilder();
// 将传过来的StringJoiner对象拼接到末尾
builder.append(other.value, other.prefix.length(), length);
}
return this;
}
2.6 length方法
如果value
不为null
,当前值=value
的长度+suffix
的长度,如果为null
,返回emptyValue
的长度。
public int length() {
// 如果value为null,返回emptyValue的长度,反之返回value的长度
return (value != null ? value.length() + suffix.length() :
emptyValue.length());
}
2.7 toString方法
如果value
不为null
,返回value
加上后缀的值,如果为null
,返回emptyValue
。
@Override
public String toString() {
// value为null用emptyValue来代替
if (value == null) {
return emptyValue;
} else {
// 没有后缀直接返回value字符串
if (suffix.equals("")) {
return value.toString();
} else {
// 有后缀需要在toString里面再补上,把当前对象作为字符串使用时,toString方法会自动调用
int initialLength = value.length();
String result = value.append(suffix).toString();
// reset value to pre-append initialLength
value.setLength(initialLength);
return result;
}
}
}
三、案例
3.1 例1
StringJoiner sj1 = new StringJoiner(",");
StringJoiner sj2 = new StringJoiner(",", "[", "]");
System.out.println(sj1.add("a").add("b").add("c"));
System.out.println(sj2.add("a").add("b").add("c"));
System.out.println(sj1.merge(sj2));
System.out.println(sj2.merge(sj1));
System.out.println(sj1.length());
输出如下:
a,b,c
[a,b,c]
a,b,c,a,b,c
[a,b,c,a,b,c,a,b,c]
11
3.2 例2
StringJoiner str = new StringJoiner(" ");
System.out.println("Initial StringJoiner: " + str);
str.setEmptyValue("StrigJoiner is empty");
// Print the StringJoiner
System.out.println("After setEmptyValue(): " + str);
// Add elements to StringJoiner
str.add("Geeks");
str.add("forGeeks");
// Print the StringJoiner
System.out.println("Final StringJoiner: " + str);
输出如下:
Initial StringJoiner:
After setEmptyValue(): StrigJoiner is empty
Final StringJoiner: Geeks forGeeks
四、拓展
如果是想将一个list
中的元素快速的以这种方式添加,可以通过String.join
来实现。
// 第一个参数是分隔符,第二个参数是list
System.out.println(String.join(",", Arrays.asList("a", "b", "c")));
// 第二个参数是可变数组
System.out.println(String.join(",", "a", "b", "c"));
输出如下:
a,b,c
a,b,c
String.join
方法也是jdk1.8
出来的
查看String.join
的源码可以看见,里面其实就是构建了一个StringJoiner
对象,它只指定了分隔符,所以String.join
不能实现有前后缀的情况。
public static String join(CharSequence delimiter,
Iterable<? extends CharSequence> elements) {
Objects.requireNonNull(delimiter);
Objects.requireNonNull(elements);
// 构建了一个指定分隔符的StringJoiner对象
StringJoiner joiner = new StringJoiner(delimiter);
// 循环添加list中的元素
for (CharSequence cs: elements) {
joiner.add(cs);
}
return joiner.toString();
}
如果想处理list
添加前后缀的问题,可以通过list
的stream
流的collect
方法来处理,需要配合Collectors.joining
方法。
Collectors.joining(CharSequence delimiter);
Collectors.joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix);
将list
通过stream
方法转成流之后调用collect
来返回想要的结果集
List<String> list1 = Arrays.asList("a", "b", "c");
List<String> list2 = Arrays.asList("a", "b", "c");
System.out.println(list1.stream().collect(Collectors.joining(",")));
System.out.println(list2.stream().collect(Collectors.joining(",", "[", "]")));
输出如下:
a,b,c
[a,b,c]
查看Collectors.joining
源码实现,发现其中也是维护了一个StringJoiner
实例去做这些事。
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);
}