文章目录
1. 介绍
- StringJoiner类是jdk8推出的,用于构造由分隔符分隔的字符序列,并可选择性地从提供的前缀开始和以提供的后缀结尾。
- 说的通俗一点:就是让字符串之间有分隔符或者字符串有前缀或者后缀,不用开发人员自己拼接。
2. 代码演示
public class demo {
public static void main(String[] args) {
// 第一个参数:分隔符
StringJoiner stringJoiner = new StringJoiner(",");
stringJoiner.add("hello")
.add("world");
System.out.println(stringJoiner.toString());
System.out.println(stringJoiner.length());
}
}
public class demo {
public static void main(String[] args) {
// 第一个参数:分隔符
// 第二个参数:前缀符
// 第三个参数:后缀符
StringJoiner stringJoiner = new StringJoiner(",","kk","dd");
stringJoiner.add("hello")
.add("world");
System.out.println(stringJoiner.toString());
System.out.println(stringJoiner.length());
}
}
细心的人发现:加了前缀kk,后缀dd,字符串长度从刚刚的11变成了15,即StringJoiner会自动帮你加上前缀后缀的长度。
public class demo {
public static void main(String[] args) {
// 不add字符串,看看会返回什么
StringJoiner stringJoiner = new StringJoiner(",","kk","dd");
System.out.println(stringJoiner.toString());
System.out.println(stringJoiner.length());
}
}
返回的是前后缀字符串拼接,因为在StringJoiner有个成员变量是emptyValue,若不指定,默认是 emptyValue = 前缀符+后缀符,而出来没有add过其他字符串,因此返回默认值emptyValue。
public class demo {
public static void main(String[] args) {
// 指定默认值
StringJoiner stringJoiner = new StringJoiner(",","kk","dd");
stringJoiner.setEmptyValue("我是默认值");
System.out.println(stringJoiner.toString());
System.out.println(stringJoiner.length());
}
}
发现指定了默认值,则不会返回前缀+后缀字符串了,因为重置了emptyValue值,返回固然是设置的值,长度更加不用多说吧
具体原因请看下面源码解析:
3. 源码
1. 成员变量
// 前缀字符
private final String prefix;
// 分隔字符
private final String delimiter;
// 后缀字符
private final String suffix;
// 拼接结果,注类型是StringBuilder
private StringBuilder value;
// 默认值,当new StringJoiner时,没有add其他字符串
// 此时toString,返回的就是emptyValue
private String emptyValue;
2. 构造函数
// 指定分隔符,前后缀默认为""
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");
this.prefix = prefix.toString();
this.delimiter = delimiter.toString();
this.suffix = suffix.toString();
// 此时 emptyValue是前后缀符的拼接
this.emptyValue = this.prefix + this.suffix;
}
3. setEmptyValue()
public StringJoiner setEmptyValue(CharSequence emptyValue) {
// 从上述构造函数可以看出,默认值是前后缀符的拼接
// 还可以使用setEmptyValue 设置自定义默认值
this.emptyValue = Objects.requireNonNull(emptyValue,
"The empty value must not be null").toString();
return this;
}
4. add()
public StringJoiner add(CharSequence newElement) {
// 添加字符串
prepareBuilder().append(newElement);
return this;
}
private StringBuilder prepareBuilder() {
if (value != null) {
// 在添加新的String的时候,先添加分隔符
value.append(delimiter);
} else {
// 如果value还没有被构造,说明是第一次add
// 第一次add,当然要先new StringBuilder(),并且加前缀
// 也可以看出 value是懒加载,不用就不new
// 当然这样子做的原因也是为了区分是否第一次add,才能合理判断是加前缀,还是分隔符。
value = new StringBuilder().append(prefix);
}
return value;
}
5. toString()
@Override
public String toString() {
// 根据上面add()源码可以知道,只有第一次add了,才会new StringBuild
// 因此 value == null 说明从来没有add字符串过
if (value == null) {
// 因此返回的是默认值
// 若没有setEmptyValue过,则返回前后缀字符串拼接的值
// 若设置过,则返回设置的值
return emptyValue;
} else {
// 走到这里,说明已经add过了
// 此时判断后缀是否为空串
if (suffix.equals("")) {
return value.toString();
} else {
// 注意:这里是StringBuilder的length()方法
// 此时 value 里面有前缀,add的字符串,可能还有分隔符
// 此时的initialLength 长度没有算上后缀字符串
int initialLength = value.length();
// 现在将后缀加上,value.length()此时是5
// 注意:此时的toString方法是StringBuilder类的
String result = value.append(suffix).toString();
// 这里却重置了,虽然 value里面包括后缀,但是长度没有算进去
// 即 value.length()等于 实际长度减后缀符长度
// 也就是 value里面不算上后缀符,本文最后再详细解析
value.setLength(initialLength);
return result;
}
}
}
6. merge()
// 合并两个StringJoiner
public StringJoiner merge(StringJoiner other) {
Objects.requireNonNull(other);
if (other.value != null) {
// 根据上面toString解析,知道value.length() 是不包括后缀的长度
final int length = other.value.length();
// 先在原来的value 加上 分隔符
StringBuilder builder = prepareBuilder();
// 再把other的字符串 添加到value里面
// 可以看出,排除了other的前缀符
builder.append(other.value, other.prefix.length(), length);
}
return this;
}
7. length()
public int length() {
// Remember that we never actually append the suffix unless we return
// the full (present) value or some sub-string or length of it, so that
// we can add on more if we need to.
// 调用StringJoiner的toString方法,此时算上后缀符长度
// 若没有add过,则返回默认值的长度
return (value != null ? value.length() + suffix.length() :
emptyValue.length());
}
8. 再次详细解释toString(),跟length()方法
上文中说到toString中有下面的逻辑:
if (suffix.equals("")) {
return value.toString();
} else {
int initialLength = value.length();
String result = value.append(suffix).toString();
value.setLength(initialLength);
return result;
}
首先要明确:value.length() 与 value.append(suffix).toString() 与 value.setLength(initialLength); 调用的是StringBuilder的方法。
在StringBuilder中的toString与setLength:
@Override
public String toString() {
// 它是new String,指定了范围的
// 打个比方:如果value为ABC
// new String(value,0,1) 则返回是A
return new String(value, 0, count);
}
public void setLength(int newLength) {
if (newLength < 0)
throw new StringIndexOutOfBoundsException(newLength);
ensureCapacityInternal(newLength);
if (count < newLength) {
Arrays.fill(value, count, newLength, '');
}
// count就是你设置的大小
count = newLength;
}
代码例子:
public class demo {
public static void main(String[] args) {
StringBuilder builder = new StringBuilder();
builder.append("ABC");
builder.setLength(1);
System.out.println(builder.toString());
}
}
发现 只返回A,实际上builder里面是ABC,但是重置了length即重置了count,因此BC相当于没有了。
重新回到这里StringJoiner的toString方法里面某个片段:
if (suffix.equals("")) {
return value.toString();
} else {
int initialLength = value.length();
String result = value.append(suffix).toString();
// value里面虽然在上一步加上了后缀字符串
// 但是这里重置了length,相当于 value 没有加上后缀字符串
value.setLength(initialLength);
return result;
}
因此在StringJoiner的length()方法里面才有 加上后缀长度的逻辑。
为什么要这样子设计了?
根据在length()方法里面的注释:
public int length() {
// Remember that we never actually append the suffix unless we return
// the full (present) value or some sub-string or length of it, so that
// we can add on more if we need to.
return (value != null ? value.length() + suffix.length() :
emptyValue.length());
}
翻译可以得:就是可以随时想添加新的字符串都可以。
代码:
public class demo {
public static void main(String[] args) {
StringJoiner stringJoiner = new StringJoiner(",", "A", "结束了");
stringJoiner.add("B").add("C");
System.out.println(stringJoiner.toString());
System.out.println(stringJoiner.length());
stringJoiner.add("D").add("F");
System.out.println(stringJoiner.toString());
System.out.println(stringJoiner.length());
}
}
可以想一想,如果toString里面没有重置value(StringBuilder)的长度,那么你toString之后,岂不是再add的时候,应该是 AB,C结束了,D,F结束了,显然不是设计的初衷