String 字符串操作新增了什么?

在高版本的 Java​ 中,字符串操作新增了如下方法:

Java 版本方法名方法描述
9chars()返回字符串的字符值流
9codePoints()返回字符串的 Unicode 值流
11strip()移除首尾空白字符(支持所有 Unicode 字符)
11stripLeading()移除头部空白字符
11stripTrailing()移除尾部空白字符
11isBlank()检查字符串是否只包含空白字符
11repeat(int count)返回一个由原始字符串重复 count​ 次组成的新字符串
11lines()使用换行符将字符串拆分为一个流
12indent(int n)根据参数 n​ 的值调整字符串的缩进
12transform(Function<? super String, ? extends R> f)使用给定的函数 f​ 对字符串进行转换
12describeConstable创建并返回一个实例本身的 Optional​ 对象
15formatted(Object... args)使用提供的参数替换占位符,并返回一个格式化的字符串
15stripIndent()移除首尾空白字符(包含换行符前后的)
15translateEscapes()将字符串中的转义序列转换为对应的字符

获取字符的 IntStream

Java 9​ 中的 String​ 类新增了 chars​ 和 codePoints​ 方法,用于获取字符的 IntStream​:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 测试内容
String testContent = "中-E-\uD835\uDD0A";

// 测试 chars
System.out.println();
System.out.println("chars:");
IntStream intStreamChars = testContent.chars();
intStreamChars.forEach(e -> System.out.print(e + "=" + (char) e + "  "));

// 测试 codePoints
System.out.println();
System.out.println("codePoints:");
IntStream intStreamCodePoints = testContent.codePoints();
intStreamCodePoints.forEach(e -> System.out.print(e + "=" + (char) e + "  "));

两者都是获取字符串中的字符 ASCII​ 码的 IntStream​ 流,但 codePoints​ 方法可以处理 Unicode​ 特殊字符,输出结果如下: ​image

空白字符处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 测试内容
String testContent = "\u2000中-E-\uD835\uDD0A-\u2000  ";

// 移除首尾空白字符(仅限 ASCII 字符)
System.out.println("text.trim():" + testContent.trim());

// Java 11 新增 - 移除首尾空白字符(支持所有 Unicode 字符)
System.out.println("text.strip():" + testContent.strip());

// Java 11 新增 - 移除头部空白字符
System.out.println("text.stripLeading():" + testContent.stripLeading());

// Java 11 新增 - 移除尾部空白字符
System.out.println("text.stripTrailing():" + testContent.stripTrailing());

// Java 15 新增 - 移除首尾空白字符(包含换行符前后的)
testContent = " \u2000中-E-回车: \n :回车 \u2000  ";
System.out.println("text.stripIndent():" + testContent.stripIndent());

输出结果: ​image

仔细看这里的 trim​ 和 strip​ 方法:

trim​ 方法仅限 ASCII​ 编码的空白字符,strip​ 可以移除所有 Unicode​ 编码的空白字符。

判断字符串是否为空

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 测试内容
String testContent = " ";

// Java 6 新增 - 判断是否为空白字符
// 输出:false
System.out.println(testContent.isEmpty());

// Java 11 新增 - 判断是否为空白字符
// 输出:true
System.out.println(testContent.isBlank());

// 空指针异常处理
// 输出:Cannot invoke "String.isBlank()" because "testContent2" is null
String testContent2 = null;
System.out.println(testContent2.isBlank());

isEmpty​ 和 isBlank​ 方法最大的区别就是对空白字符串的判断,如果字符串仅仅包含空白字符,isEmpty​ 为真,isBlank​ 为假。

如果字符串为 null​,使用这两个方法都会抛出空指针异常。

重复拼接字符串

Java 11​ 中新添加重复拼接字符串 repeat​ 方法,比如下面示例:

1
2
String testContent = "hello~ ";
System.out.println(testContent.repeat(3));

输出结果: ​image

其底层调用了 System.arraycopy​ 方法进行字节拷贝,最后再封装成一个新的字符串。

换行符拆分流

Java 11​ 新增方法。根据字符串的换行符,将每个字符形成一个 Stream​ 流:

1
2
3
4
5
6
// 测试内容
String testContent = "hello\njava\nC++\nPHP\nGO\n";
// 返回 Stream 流的数量
System.out.println("数量:" + testContent.lines().count());
// 遍历 Stream 流输出
testContent.lines().forEach(System.out::println);

输出结果: ​image

字符串缩进

indent​ 是 Java 12​ 新增方法,用于对字符串进行缩进:

1
2
String testContent = "hello\njava\nC++\nPHP\nGO\n";
System.out.println(testContent.indent(3));

输出结果: ​image

来看它的源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
 * 缩进字符串
 * @param n 缩进的空格数
 * @return 缩进后的字符串
 */
public String indent(int n) {
    // 如果字符串为空,则直接返回空字符串
    if (isEmpty()) {
        return "";
    }
    // 获取每一行字符串
    Stream<String> stream = lines();
    // 如果缩进数大于 0
    if (n > 0) {
        // 生成包含 n 个空格的字符串
        final String spaces = " ".repeat(n);
        // 对每一行字符串进行处理,将其前面添加 n 个空格
        stream = stream.map(s -> spaces + s);
    } else if (n == Integer.MIN_VALUE) {
        // 对每一行字符串进行处理,去除开头的空格
        stream = stream.map(s -> s.stripLeading());
    } else if (n < 0) {
        // 对每一行字符串进行处理,截取从开头到第一个非空格字符之间的子字符串
        stream = stream.map(s -> s.substring(Math.min(-n, s.indexOfNonWhitespace())));
    }
    // 将每一行字符串通过换行符连接起来,并返回结果
    return stream.collect(Collectors.joining("\n", "", "\n"));
}

其实就是调用了上面的 lines​​ 方法根据根据换行符来创建一个 Stream​,然后再往前拼接指定数量的空格,最后形成新的字符串。

字符串转换

transform​ 是 Java 12​ 新增的方法,用于字符串转换:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 测试内容
String testContent = " 自本非美玉,故不敢加以刻苦雕琢 ";

/*
 * 对测试内容进行处理:
 * 1. 移除首尾空白字符(strip 方法)
 * 2. 转换为大写(toUpperCase 方法)
 * 3. 将处理后的字符串拼接为 "我深怕" 开头的新字符串
 */
String result = testContent.transform(String::strip)
        .transform(String::toUpperCase)
        .transform(e -> "我深怕" + e);


System.out.println(result);

这里先用 strip​ 移除前后的空白字符,然后转换为大写,最后再拼接一个前缀。

结果输出: ​image

返回实例本身的 Optional 对象

Java 12​ 中的 String​ 实现了 Constable​ 接口:

1
2
3
4
5
6
7
8
9
public interface Constable {
    /*
     * 如果可以构建该实例的名义描述符,则返回一个包含它的 {@link Optional} 对象
     * 若无法构建,则返回一个空的 {@link Optional} 对象。
     *
     * @return 包含生成的名义描述符的 {@link Optional} 对象,如果无法构建则返回一个空的 {@link Optional} 对象。
     */
    Optional<? extends ConstantDesc> describeConstable();
}

实现源码如下所示:

1
2
3
4
5
6
7
8
9
/*
 * 返回一个包含当前对象的 {@link Optional} 对象。
 *
 * @return 包含当前对象的 {@link Optional} 对象。
 */
@Override
public Optional<String> describeConstable() {
    return Optional.of(this);
}

其实就是把字符串本身封装一个 Optional​ 对象返回。

示例代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 测试内容
String testContent = "却又半信自己是块美玉,故又不肯庸庸碌碌,与瓦砾为伍";

Optional<String> nameOptional = testContent.describeConstable();

// 直接 get 会提示如下信息:
// 'Optional.get()' without 'isPresent()' check
System.out.println(nameOptional.get());

// 此处相当于:
// 1. 使用 'isPresent()' 方法检查 Optional 是否包含值
// 2. 打印 Optional 中的值
// if (nameOptional.isPresent()) {
//     System.out.println(nameOptional.get());
// }
nameOptional.ifPresent(System.out::println);

输出结果:

却又半信自己是块美玉,故又不肯庸庸碌碌,与瓦砾为伍

Optional​ 是 Java 8​ 中的新特性,可用来替代对象的 != null​ 判断。

字符串格式化

Java 15​ 中的 String​​ 类引入了一个 formatted()​​ 格式化新方法,它是 String.format()​​ 方法的简写版本,它可以方便地对字符串进行格式化。

1
2
3
4
5
6
String testContent1 = "Hello, %s".formatted("Linux do!");
System.out.println(testContent1);

// 白白白是一只雪白的猫猫
String testContent2 = "我叫 %s,我今年已经 %d 岁啦~".formatted("白白白", 3);
System.out.println(testContent2);

输出结果: ​image

字符串转义

Java 15​ 中,String​​ 类引入了 translateEscapes()​​ 新方法,用于将字符串中的转义序列转化为相应的字符。

来看下面的示例代码:

1
2
3
4
5
6
7
8
String testContent = "Hello\n\\n白白白\\t!";

System.out.println("===testContent===");
System.out.println(testContent);

System.out.println();
System.out.println("===translateEscapes===");
System.out.println(testContent.translateEscapes());

输出结果: ​image

通过前面输出对比可以看出,正常的转义字符 \\n​ 经过转换一次之后变成了 \n​ 就不能再转换了,而新出的 translateEscapes​ 方法可以继续转换出转义之后的转义字符,把所有转义字符转换为真正的字符。

支持的转义字符如下:

转义序列对应的字符
\b退格符 (Backspace)
\t制表符 (Tab)
\n换行符 (Newline)
\f换页符 (Form feed)
\r回车符 (Carriage return)
\"双引号 (Double quote)
\'单引号 (Single quote)
\\反斜线 (Backslash)
\0​ to \377八进制表示的 Unicode 字符
\s空格符 (Space)