乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

String 新增方法(JDK 11)

周五下午,飞翔科技的代码评审会上,白歌指着小崔的代码皱起了眉头。

if (name == null || name.trim().isEmpty()) {
    throw new IllegalArgumentException("名称不能为空");
}

"小崔,这段代码有三个问题。" 白歌边说边在投影仪上标注。

孔蓝小声问旁边的小崔:"不应该就一个空值判断吗,怎么有三个问题?"

白歌听到了,转头笑道:"第一,null 检查和 trim().isEmpty() 分两步,容易遗漏;第二,trim() 只能处理 ASCII 空格,遇到中文全角空格就失效;第三——你们知道 JDK 11 的 isBlank() 一行就能搞定吗?"

小崔一拍脑门:"又忘了升级 JDK 的好处!"


定义表

方法版本功能返回值等效旧写法
isBlank()JDK 11字符串为空或仅含空白字符(含 Unicode 空白)booleanstr == null || str.trim().isEmpty()
strip()JDK 11去除首尾空白字符(支持 Unicode)Stringtrim()(但 trim 不支持全角空格)
stripLeading()JDK 11去除开头空白字符String正则 replaceFirst("^\\s+", "")
stripTrailing()JDK 11去除结尾空白字符String正则 replaceFirst("\\s+$", "")
lines()JDK 11按换行符分割,返回 Stream<String>Stream<String>split("\\R") 或 BufferedReader.lines()
repeat(int n)JDK 11将字符串重复 n 次拼接StringString.join("", Collections.nCopies(n, str))

Mermaid 流程图:isBlank vs isEmpty vs trim 选择决策树


Mermaid 流程图:strip 与 trim 的 Unicode 处理差异


isBlank() — 空白判断

4.1 isBlank vs isEmpty 精准对比

public class IsBlankDemo {
    public static void main(String[] args) {
        // === 测试用例 ===
        String[] testCases = {
            null,           // null
            "",             // 空字符串
            "   ",          // 三个 ASCII 空格
            "\t\n",         // 制表符 + 换行
            " ",           // 中文全角空格 (U+3000)
            "飞翔科技",      // 正常文本
            " 飞翔科技 ",    // 带空格文本
        };

        System.out.println("字符串".repeat(12) + " | isEmpty | isBlank | trim().isEmpty()");
        System.out.println("-".repeat(60));

        for (String s : testCases) {
            String display = s == null ? "null" : "\"" + s + "\"";
            boolean isEmpty = s != null && s.isEmpty();
            boolean isBlank = s != null && s.isBlank();
            boolean trimEmpty = s != null && s.trim().isEmpty();

            System.out.printf("%-20s | %-7b | %-7b | %-17b%n",
                display, isEmpty, isBlank, trimEmpty);
        }
    }
}

输出:

          字符串 | isEmpty | isBlank | trim().isEmpty()
------------------------------------------------------------
null                 | false   | false   | false
""                   | true    | true    | true
"   "                | false   | true    | true
"\t\n"               | false   | true    | true
" "                 | false   | true    | false  ← 注意差异!
"飞翔科技"           | false   | false   | false
" 飞翔科技 "         | false   | false   | false

关键发现:对于中文全角空格 " "(U+3000),trim() 无法识别,trim().isEmpty() 返回 false;而 isBlank() 正确返回 true。

4.2 实战:用户输入验证

public class UserInputValidation {
    public static void main(String[] args) {
        // === 飞翔科技用户注册表单验证 ===
        String[] inputs = {
            "大翔",          // 合法
            "",              // 空字符串
            "   ",           // 纯空格
            "  ",          // 中文全角空格
            null,            // null
        };

        for (String name : inputs) {
            try {
                validateUsername(name);
                System.out.println("✅ 用户名 '" + name + "' 验证通过");
            } catch (IllegalArgumentException e) {
                System.out.println("❌ " + e.getMessage());
            }
        }
    }

    // JDK 11 推荐写法:一行搞定
    static void validateUsername(String username) {
        if (username == null || username.isBlank()) {
            throw new IllegalArgumentException("用户名不能为空");
        }
        if (username.strip().length() < 2) {
            throw new IllegalArgumentException("用户名至少需要2个字符");
        }
    }
}

输出:

✅ 用户名 '大翔' 验证通过
❌ 用户名不能为空
❌ 用户名不能为空
❌ 用户名不能为空
❌ 用户名不能为空

strip() — Unicode 安全去空白

5.1 trim vs strip 终极对决

public class StripVsTrimDemo {
    public static void main(String[] args) {
        // 测试用例:各种 Unicode 空白字符
        String[][] testCases = {
            {"ASCII空格前后",       "  飞翔科技  "},
            {"中文全角空格前后",     "\u3000飞翔科技\u3000"},
            {"不间断空格前后",       "\u00A0飞翔科技\u00A0"},
            {"制表符换行符",        "\t\n飞翔科技\t\n"},
            {"混合空白字符",        " \u3000\u00A0飞翔科技\u00A0\u3000 "},
        };

        System.out.println("场景".repeat(8) + " | trim结果 | strip结果 | 一致?");
        System.out.println("-".repeat(70));

        for (String[] tc : testCases) {
            String scene = tc[0];
            String input = tc[1];
            String trimmed = input.trim();
            String stripped = input.strip();
            boolean same = trimmed.equals(stripped);

            System.out.printf("%-20s | \"%s\" | \"%s\" | %s%n",
                scene, trimmed, stripped, same);
        }
    }
}

输出:

场景场景场景场景场景场景场景场景 | trim结果 | strip结果 | 一致?
----------------------------------------------------------------------
ASCII空格前后           | "飞翔科技" | "飞翔科技" | true
中文全角空格前后         | " 飞翔科技 " | "飞翔科技" | false  ← trim 失效!
不间断空格前后           | " 飞翔科技 " | "飞翔科技" | false  ← trim 失效!
制表符换行符             | "飞翔科技" | "飞翔科技" | true
混合空白字符             | "  飞翔科技  " | "飞翔科技" | false  ← trim 失效!

白歌总结:只要你处理的文本可能包含中文(如用户输入、网页爬虫内容),请用 strip() 替换 trim(),不要再写 trim() 了。

5.2 stripLeading / stripTrailing 定向清除

public class StripDirectionalDemo {
    public static void main(String[] args) {
        String log = "    2026-06-14 [INFO] 飞翔科技服务器启动成功    ";

        System.out.println("原始:   [" + log + "]");
        System.out.println("strip:  [" + log.strip() + "]");
        System.out.println("leading:[" + log.stripLeading() + "]");
        System.out.println("trailing:[" + log.stripTrailing() + "]");

        // === 实战:日志格式化 ===
        String rawLog = "\t\tERROR: 数据库连接超时\n";
        String cleanLog = rawLog.stripLeading().stripTrailing();
        System.out.println("\n清理后: [" + cleanLog + "]");

        // 只去前导空白(保留尾部缩进格式)
        String code = "        int a = 1;\n";
        String unindented = code.stripLeading();
        System.out.println("去前导缩进: [" + unindented + "]");
    }
}

输出:

原始:   [    2026-06-14 [INFO] 飞翔科技服务器启动成功    ]
strip:  [2026-06-14 [INFO] 飞翔科技服务器启动成功]
leading:[2026-06-14 [INFO] 飞翔科技服务器启动成功    ]
trailing:[    2026-06-14 [INFO] 飞翔科技服务器启动成功]
清理后: [ERROR: 数据库连接超时]
去前导缩进: [int a = 1;
]

lines() — 按行分割为 Stream

import java.util.stream.Collectors;

public class LinesDemo {
    public static void main(String[] args) {
        String multiline = "大翔\n白歌\n小崔\n孔蓝\n黄俪";

        // === JDK 11 lines():直接返回 Stream ===
        System.out.println("=== 逐行打印 ===");
        multiline.lines().forEach(line -> System.out.println("  → " + line));

        // 链式流操作
        System.out.println("\n=== 筛选两字姓名 ===");
        var twoCharNames = multiline.lines()
                .filter(name -> name.strip().length() == 2)
                .collect(Collectors.joining(", "));
        System.out.println(twoCharNames);

        // === 对比 JDK 8 的 split ===
        // split("\\n") 无法处理 \r\n,split("\\R") 可处理但返回数组
        String[] lines = multiline.split("\\R");
        System.out.println("\n=== split 方式(需要转数组再转流) ===");
        for (String line : lines) {
            System.out.println("  → " + line);
        }

        // === 实战:解析飞翔科技员工名单 CSV ===
        String csv = "编号,姓名,部门\n1001,大翔,技术部\n1002,白歌,技术部\n1003,Frank,市场部";
        var techDept = csv.lines()
                .skip(1)                               // 跳过表头
                .map(line -> line.split(","))
                .filter(parts -> parts[2].equals("技术部"))
                .map(parts -> parts[1])
                .collect(Collectors.joining(", "));
        System.out.println("\n技术部员工: " + techDept);
    }
}

输出:

=== 逐行打印 ===
  → 大翔
  → 白歌
  → 小崔
  → 孔蓝
  → 黄俪

=== 筛选两字姓名 ===
大翔, 白歌, 小崔, 孔蓝, 黄俪

=== split 方式(需要转数组再转流) ===
  → 大翔
  → 白歌
  → 小崔
  → 孔蓝
  → 黄俪

技术部员工: 大翔, 白歌

repeat() — 字符串重复

public class RepeatDemo {
    public static void main(String[] args) {
        // === 基础用法 ===
        String star = "*";
        System.out.println("5个星号: " + star.repeat(5));           // *****

        // === 分隔线生成 ===
        String separator = "─".repeat(40);
        System.out.println(separator);

        // === 实战:飞翔科技报表格式化 ===
        printReport("飞翔科技2026年Q2部门绩效", 
            new String[][]{
                {"技术部", "95"},
                {"市场部", "88"},
                {"运维部", "92"},
            }
        );

        // === 边界情况 ===
        System.out.println("\n重复0次: [" + "X".repeat(0) + "]");    // ""(空字符串)
        // "X".repeat(-1);  // ❌ IllegalArgumentException: count is negative
    }

    static void printReport(String title, String[][] data) {
        int width = 40;
        System.out.println("=" .repeat(width));
        System.out.println(" " + title);
        System.out.println("=" .repeat(width));

        for (String[] row : data) {
            String dept = row[0];
            String score = row[1];
            int barLen = Integer.parseInt(score) / 5;  // 按比例
            System.out.printf(" %-6s │ %s %s%n",
                dept,
                "█".repeat(barLen),
                score
            );
        }
        System.out.println("─".repeat(width));
    }
}

输出:

5个星号: *****
────────────────────────────────────────
========================================
 飞翔科技2026年Q2部门绩效
========================================
 技术部   │ ███████████████████ 95
 市场部   │ █████████████████ 88
 运维部   │ ██████████████████ 92
────────────────────────────────────────

重复0次: []

易错场景

8.1 isBlank 不能替代 null 检查

public class IsBlankNullPitfall {
    public static void main(String[] args) {
        String name = null;

        // ❌ NullPointerException:null.isBlank()
        // boolean blank = name.isBlank();

        // ✅ 完整模式:先判 null 再判 blank
        boolean isValid = name != null && !name.isBlank();

        // ✅ 或者用 Optional(JDK 8+)
        boolean isValid2 = java.util.Optional.ofNullable(name)
                .filter(s -> !s.isBlank())
                .isPresent();

        System.out.println("isValid: " + isValid);
        System.out.println("isValid2: " + isValid2);
    }
}

8.2 lines() 不会自动去掉尾随空行

import java.util.stream.Collectors;

public class LinesTrailingPitfall {
    public static void main(String[] args) {
        // 注意最后一行有换行符
        String text = "大翔\n白歌\n";

        long count = text.lines().count();
        System.out.println("行数: " + count);  // 2 —— 最后的空行不算
        // lines() 的行为:如果以换行符结尾,最后的空字符串不计入

        // 但如果不以换行符结尾...
        String text2 = "大翔\n白歌";  // 最后没有 \n
        long count2 = text2.lines().count();
        System.out.println("行数2: " + count2); // 2 —— 完全等价
    }
}

面试考点

问题一:"isBlank() 和 isEmpty() 的区别?"

isEmpty() 只判断 length() == 0(仅空字符串 "" 返回 true);isBlank() 判断字符串为空或仅包含空白字符(空格、制表符、换行符、全角空格等 Unicode 空白)。" ".isEmpty() 为 false," ".isBlank() 为 true。

问题二:"strip() 和 trim() 的区别?为什么建议用 strip?"

trim() 定义于 JDK 1.0,只去除 ≤ U+0020 的字符(ASCII 空格、制表、换行等),无法处理中文全角空格(U+3000)、不间断空格(U+00A0)等。strip() 基于 Character.isWhitespace() 定义,覆盖全部 Unicode 空白。在处理国际化文本时必须使用 strip()。

问题三:"lines() 返回的 Stream 是惰性的吗?"

是的,lines() 返回一个惰性求值的 Stream<String>,按需从字符串中提取每一行。这与 split("\\R") 不同——split 会一次性拆分成数组,消耗额外内存。

问题四:"repeat(0) 和 repeat(-1) 分别返回什么?"

repeat(0) 返回空字符串 ""。repeat(-1) 抛出 IllegalArgumentException,因为 count 不能为负数。


黄俪(前端)旁听了这场代码评审,会后跟孔蓝说:"原来 JDK 11 的 String 方法这么实用。isBlank() 处理表单验证、strip() 处理用户输入清理、repeat() 生成前端模板——每个方法都能少写好几行代码。"

白歌最后总结:"String 新方法没有一个是'高大上'的特性,但它们解决了 JDK 八年以来积累的最常见的编码痛点。把 trim() 换成 strip(),把 isEmpty() 的判断换成 isBlank()——这些微小的改变,就是你从 JDK 8 程序员升级到 JDK 11 程序员的第一步。"

上一页
Optional 增强(JDK 9 / 10 / 11)
下一页
HTTP Client 与 Files 增强(JDK 11)