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

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

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

获取字符的 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)