Java工具类之StringJoiner源码分析

一、概述

StringJoinerjava.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方法,那么valuenull

在调用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添加前后缀的问题,可以通过liststream流的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);
}

参考文章

posted @ 2022-06-02 22:37  夏尔_717  阅读(87)  评论(0编辑  收藏  举报