背景

本文是《Java 后端从小白到大神》修仙系列第十四篇,正式进入Java后端世界,本篇文章主要聊Java基础。若想详细学习请点击首篇博文,我们开始吧。

文章概览

  1. Arrays 类 - 数组操作工具
  2. Collections 类 - 集合操作工具
  3. Objects 类 - 对象安全操作
  4. Files/Paths 类 - NIO 文件操作
  5. Math 类 - 数学计算工具
  6. Stream API - 函数式数据处理
  7. StringUtils - Apache 字符串工具

一、Arrays 类

1.1 核心定位

Arrays 是 Java 提供的数组工具类,包含操作数组的静态方法。理解其内部实现有助于写出高性能代码。

1.2 常用方法详解

方法 时间复杂度 空间复杂度 核心用途
sort() O(n log n) O(log n) 双轴快排(基本类型)/ TimSort(对象)
binarySearch() O(log n) O(1) 二分查找(需先排序)
copyOf() O(n) O(n) 数组扩容/缩容
fill() O(n) O(1) 批量填充默认值
equals() O(n) O(1) 深度比较数组内容
asList() O(1) O(1) 数组转固定大小 List

1.3 代码示例与原理分析

 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import java.util.Arrays;
import java.util.List;

public class ArraysDemo {
    
    public static void main(String[] args) {
        // ========== 排序 ==========
        int[] arr = {5, 3, 1, 4, 2};
        
        // 基本类型:Dual-Pivot Quicksort,平均 O(n log n)
        Arrays.sort(arr);
        System.out.println(Arrays.toString(arr));  // [1, 2, 3, 4, 5]
        
        // 对象数组:TimSort(稳定排序),O(n log n)
        String[] strs = {"banana", "apple", "cherry"};
        Arrays.sort(strs);
        
        // ========== 二分查找 ==========
        // 前提:数组必须已排序
        int index = Arrays.binarySearch(arr, 4);  // 返回 3
        
        // 未找到时返回 (-(插入点) - 1)
        int notFound = Arrays.binarySearch(arr, 6);  // 返回 -6
        
        // ========== 数组拷贝 ==========
        // 扩容:新长度 > 原长度,补默认值
        int[] expanded = Arrays.copyOf(arr, 8);  // [1, 2, 3, 4, 5, 0, 0, 0]
        
        // 缩容:新长度 < 原长度,截断
        int[] truncated = Arrays.copyOf(arr, 3);  // [1, 2, 3]
        
        // 范围拷贝:原数组不变,性能优于循环复制
        int[] rangeCopy = Arrays.copyOfRange(arr, 1, 4);  // [2, 3, 4]
        
        // ========== 数组转 List(重要陷阱)==========
        // 返回的是 Arrays$ArrayList,不是 java.util.ArrayList
        List<Integer> list = Arrays.asList(1, 2, 3);
        
        // 支持 get/set,但不支持 add/remove
        // list.add(4);  // 抛出 UnsupportedOperationException
        
        // 正确做法:重新包装
        List<Integer> mutableList = new ArrayList<>(Arrays.asList(1, 2, 3));
        mutableList.add(4);  // 正常执行
        
        // ========== 多维数组操作 ==========
        int[][] matrix = {{1, 2}, {3, 4}};
        
        // deepToString:正确处理嵌套数组
        System.out.println(Arrays.deepToString(matrix));  // [[1, 2], [3, 4]]
        
        // deepEquals:深度比较多维数组
        int[][] matrix2 = {{1, 2}, {3, 4}};
        System.out.println(Arrays.deepEquals(matrix, matrix2));  // true
        
        // ========== Stream 操作 ==========
        int sum = Arrays.stream(arr).sum();           // 15
        double avg = Arrays.stream(arr).average().orElse(0);  // 3.0
        int max = Arrays.stream(arr).max().orElse(0);  // 5
    }
}

1.4 性能陷阱与最佳实践

 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
public class ArraysBestPractice {
    
    // 陷阱1:频繁扩容
    public void badPractice() {
        int[] arr = new int[0];
        for (int i = 0; i < 1000; i++) {
            arr = Arrays.copyOf(arr, arr.length + 1);  // O(n²) 复杂度!
            arr[i] = i;
        }
    }
    
    // 正确做法:预估容量
    public void goodPractice() {
        int[] arr = new int[1000];  // 一次性分配
        for (int i = 0; i < 1000; i++) {
            arr[i] = i;
        }
    }
    
    // 陷阱2:asList 修改原数组
    public void asListTrap() {
        String[] array = {"a", "b", "c"};
        List<String> list = Arrays.asList(array);
        
        array[0] = "x";  // 修改原数组
        System.out.println(list.get(0));  // "x",List 也被修改了!
    }
}

二、Collections 类

2.1 核心定位

Collections 提供对集合的静态工具方法,包括排序、查找、同步包装、不可变包装等。

2.2 方法分类

类别 代表方法 用途
排序 sort(), reverse(), shuffle() 改变元素顺序
查找 binarySearch(), max(), min() 极值与搜索
线程安全 synchronizedXxx() 包装线程安全集合
不可变 unmodifiableXxx() 创建只读视图
单例 singletonXxx() 创建单元素集合

2.3 代码示例

 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import java.util.*;

public class CollectionsDemo {
    
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>(Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6));
        
        // ========== 排序与查找 ==========
        Collections.sort(list);           // [1, 1, 2, 3, 4, 5, 6, 9]
        Collections.reverse(list);        // [9, 6, 5, 4, 3, 2, 1, 1]
        Collections.shuffle(list);        // 随机打乱顺序
        
        int max = Collections.max(list);  // 9
        int min = Collections.min(list);  // 1
        
        // 二分查找(需先排序)
        Collections.sort(list);
        int index = Collections.binarySearch(list, 5);  // 返回索引
        
        // ========== 线程安全包装 ==========
        // 非线程安全的 ArrayList
        List<String> unsafeList = new ArrayList<>();
        
        // 包装为线程安全版本(所有方法加 synchronized)
        List<String> safeList = Collections.synchronizedList(unsafeList);
        
        // 注意:迭代时仍需手动同步
        synchronized (safeList) {
            for (String s : safeList) {
                System.out.println(s);
            }
        }
        
        // ========== 不可变集合 ==========
        // 创建只读视图,任何修改操作抛异常
        List<String> readOnly = Collections.unmodifiableList(
            new ArrayList<>(Arrays.asList("a", "b"))
        );
        // readOnly.add("c");  // UnsupportedOperationException
        
        // 创建单元素集合(节省内存)
        Set<String> singleton = Collections.singleton("only");
        List<String> singleList = Collections.singletonList("only");
        Map<String, Integer> singleMap = Collections.singletonMap("key", 1);
        
        // ========== 填充与替换 ==========
        List<String> filled = new ArrayList<>(Arrays.asList("a", "b", "c"));
        Collections.fill(filled, "x");  // [x, x, x]
        
        // 替换所有匹配元素
        List<Integer> nums = new ArrayList<>(Arrays.asList(1, 2, 1, 3, 1));
        Collections.replaceAll(nums, 1, 9);  // [9, 2, 9, 3, 9]
        
        // ========== 频率统计 ==========
        int freq = Collections.frequency(nums, 9);  // 3
        
        // ========== 集合运算 ==========
        List<String> list1 = new ArrayList<>(Arrays.asList("a", "b", "c"));
        List<String> list2 = new ArrayList<>(Arrays.asList("b", "c", "d"));
        
        // 是否有交集
        boolean disjoint = Collections.disjoint(list1, list2);  // false
        
        // 复制(目标必须足够大)
        List<String> dest = new ArrayList<>(Collections.nCopies(list1.size(), ""));
        Collections.copy(dest, list1);  // dest = [a, b, c]
    }
}

2.4 线程安全 vs 并发集合

 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
29
30
import java.util.*;
import java.util.concurrent.*;

public class ThreadSafeComparison {
    
    public static void main(String[] args) {
        // 方式1:Collections.synchronizedList
        // 原理:每个方法加 synchronized 锁
        // 优点:简单,兼容旧代码
        // 缺点:锁粒度粗,并发性能差
        List<String> syncList = Collections.synchronizedList(new ArrayList<>());
        
        // 方式2:CopyOnWriteArrayList(推荐读多写少场景)
        // 原理:写操作复制新数组,读操作无锁
        // 优点:读性能极高,无并发读问题
        // 缺点:写性能差,内存开销大
        List<String> cowList = new CopyOnWriteArrayList<>();
        
        // 方式3:ConcurrentLinkedQueue(高并发场景)
        // 原理:CAS 无锁算法
        // 优点:高并发性能好
        // 缺点:size() 操作需要遍历
        Queue<String> concurrentQueue = new ConcurrentLinkedQueue<>();
        
        // 选择建议:
        // - 读多写少:CopyOnWriteArrayList
        // - 读写均衡:Collections.synchronizedList 或 Vector
        // - 高并发队列:ConcurrentLinkedQueue / LinkedBlockingQueue
    }
}

三、Objects 类

3.1 核心定位

Objects 类提供空安全的对象操作方法,是防御式编程的重要工具。

3.2 核心方法

方法 用途 替代方案
equals(a, b) 空安全比较 a != null && a.equals(b)
hashCode(o) 空安全哈希 o != null ? o.hashCode() : 0
requireNonNull(o) 参数校验 if (o == null) throw ...
isNull(o) / nonNull(o) 空值判断 o == null / o != null
compare(a, b, c) 空安全比较 自定义比较器

3.3 代码示例

 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import java.util.Objects;

public class ObjectsDemo {
    
    public static void main(String[] args) {
        String str1 = null;
        String str2 = "Hello";
        String str3 = "Hello";
        
        // ========== 空安全比较 ==========
        // 传统写法
        boolean oldWay = str1 != null && str1.equals(str2);  // false
        
        // Objects 写法(更简洁,避免 NPE)
        boolean newWay = Objects.equals(str1, str2);  // false
        boolean bothNull = Objects.equals(null, null);  // true
        
        // ========== 参数校验 ==========
        // 基础校验
        String name = Objects.requireNonNull(str2, "Name cannot be null");
        
        // 带条件校验(Java 8+)
        String validated = Objects.requireNonNull(str2, () -> "Name is required at " + System.currentTimeMillis());
        
        // 实际应用:构造函数参数校验
        User user = new User(null);  // 抛出 NullPointerException: Username cannot be null
        
        // ========== 哈希计算 ==========
        // 单对象哈希
        int hash1 = Objects.hashCode(str2);
        
        // 多字段组合哈希(用于重写 hashCode)
        int combinedHash = Objects.hash("field1", 123, true);
        
        // ========== 空值判断(用于 Stream 过滤)==========
        List<String> list = Arrays.asList("a", null, "b", null, "c");
        
        long nullCount = list.stream()
            .filter(Objects::isNull)
            .count();  // 2
        
        List<String> nonNullList = list.stream()
            .filter(Objects::nonNull)
            .collect(Collectors.toList());  // [a, b, c]
        
        // ========== 空安全比较器 ==========
        List<String> strings = Arrays.asList("banana", null, "apple", "cherry", null);
        
        // nullsFirst:null 排前面
        strings.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
        // [null, null, apple, banana, cherry]
        
        // nullsLast:null 排后面
        strings.sort(Comparator.nullsLast(Comparator.naturalOrder()));
        // [apple, banana, cherry, null, null]
    }
    
    static class User {
        private final String username;
        
        public User(String username) {
            // 防御式编程:尽早失败
            this.username = Objects.requireNonNull(username, "Username cannot be null");
        }
        
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            User user = (User) o;
            return Objects.equals(username, user.username);
        }
        
        @Override
        public int hashCode() {
            return Objects.hash(username);
        }
    }
}

四、Files 与 Paths 类(NIO.2)

4.1 核心定位

Java 7 引入的 NIO.2 文件操作 API,替代传统的 File 类,提供更强大的文件操作能力。

4.2 核心方法对比

操作 传统 IO (File) NIO.2 (Files/Paths)
创建路径 new File("path") Paths.get("path")
读文件 FileInputStream Files.readAllBytes() / Files.readAllLines()
写文件 FileOutputStream Files.write()
复制 手动流复制 Files.copy()
遍历目录 listFiles() Files.walk() / Files.find()
文件属性 canRead(), length() Files.isReadable(), Files.size()

4.3 代码示例

  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
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.util.stream.Stream;

public class FilesDemo {
    
    public static void main(String[] args) throws IOException {
        // ========== 路径操作 ==========
        // 创建 Path 对象(不实际创建文件)
        Path path = Paths.get("/tmp", "test", "file.txt");
        Path relative = Paths.get("src", "main", "java");
        
        // 路径解析
        Path parent = path.getParent();      // /tmp/test
        Path fileName = path.getFileName();  // file.txt
        Path root = path.getRoot();          // /
        
        // 路径拼接
        Path resolved = Paths.get("/tmp").resolve("test/file.txt");  // /tmp/test/file.txt
        Path relativized = Paths.get("/tmp").relativize(Paths.get("/tmp/test/file.txt"));  // test/file.txt
        
        // ========== 文件读写 ==========
        Path tempFile = Files.createTempFile("demo", ".txt");
        
        // 写入(自动创建父目录,自动关闭流)
        String content = "Hello, Java NIO!";
        Files.write(tempFile, content.getBytes(StandardCharsets.UTF_8));
        
        // 追加写入
        Files.write(tempFile, "\nSecond line".getBytes(), StandardOpenOption.APPEND);
        
        // 读取所有字节
        byte[] bytes = Files.readAllBytes(tempFile);
        String readContent = new String(bytes, StandardCharsets.UTF_8);
        
        // 读取所有行(适合文本文件)
        List<String> lines = Files.readAllLines(tempFile, StandardCharsets.UTF_8);
        
        // 使用 Stream 逐行读取(大文件推荐)
        try (Stream<String> stream = Files.lines(tempFile)) {
            stream.filter(line -> line.contains("Java"))
                  .forEach(System.out::println);
        }
        
        // ========== 文件复制与移动 ==========
        Path source = Paths.get("source.txt");
        Path target = Paths.get("target.txt");
        
        // 复制(可指定选项)
        Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
        
        // 原子移动(同分区瞬间完成)
        Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
        
        // ========== 目录遍历 ==========
        Path start = Paths.get("/tmp");
        
        // 遍历所有层级
        try (Stream<Path> paths = Files.walk(start, 3)) {  // 最大深度3
            paths.filter(Files::isRegularFile)
                 .filter(p -> p.toString().endsWith(".txt"))
                 .forEach(System.out::println);
        }
        
        // 按条件查找
        try (Stream<Path> paths = Files.find(start, 3, 
                (p, attrs) -> attrs.isRegularFile() && p.toString().endsWith(".java"))) {
            paths.forEach(System.out::println);
        }
        
        // ========== 文件属性 ==========
        Path file = Paths.get("/tmp/test.txt");
        
        // 基本属性
        boolean exists = Files.exists(file);
        boolean readable = Files.isReadable(file);
        boolean writable = Files.isWritable(file);
        long size = Files.size(file);
        
        // 时间戳
        FileTime lastModified = Files.getLastModifiedTime(file);
        
        // 文件类型探测
        String contentType = Files.probeContentType(file);  // 如 "text/plain"
        
        // ========== 创建与删除 ==========
        Path newDir = Paths.get("/tmp/newdir");
        
        // 创建目录(父目录必须存在)
        Files.createDirectory(newDir);
        
        // 创建多级目录
        Files.createDirectories(Paths.get("/tmp/a/b/c"));
        
        // 创建空文件
        Path newFile = Files.createFile(Paths.get("/tmp/newdir/file.txt"));
        
        // 创建临时文件/目录
        Path temp = Files.createTempFile("prefix", ".suffix");
        Path tempDir = Files.createTempDirectory("mydir");
        
        // 删除(文件必须存在)
        Files.delete(newFile);
        
        // 静默删除(不存在不抛异常)
        Files.deleteIfExists(newFile);
    }
}

4.4 异常处理与资源管理

 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import java.nio.file.*;
import java.io.IOException;

public class FilesExceptionHandling {
    
    public static void safeFileOperation() {
        Path path = Paths.get("/tmp/data.txt");
        
        // 方式1:传统 try-catch
        try {
            String content = Files.readString(path);
            System.out.println(content);
        } catch (NoSuchFileException e) {
            System.err.println("文件不存在: " + e.getFile());
        } catch (AccessDeniedException e) {
            System.err.println("权限不足: " + e.getFile());
        } catch (IOException e) {
            System.err.println("IO 错误: " + e.getMessage());
        }
        
        // 方式2:使用 try-with-resources 确保流关闭
        try (var lines = Files.lines(path)) {
            lines.forEach(System.out::println);
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // 方式3:先检查再操作(避免异常)
        if (Files.exists(path) && Files.isReadable(path)) {
            try {
                String content = Files.readString(path);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    // 原子文件写入(防止写入中断导致文件损坏)
    public static void atomicWrite(Path path, String content) throws IOException {
        Path temp = Files.createTempFile("temp", ".tmp");
        try {
            Files.writeString(temp, content);
            Files.move(temp, path, StandardCopyOption.ATOMIC_MOVE, 
                      StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            Files.deleteIfExists(temp);
            throw e;
        }
    }
}

五、Math 类

5.1 核心定位

Math 类提供基本数学运算,但需要注意其局限性和精度问题。

5.2 方法分类

类别 方法 说明
极值 max(), min() 比较大小
绝对值 abs() 处理负数
幂运算 pow(), sqrt(), cbrt() 平方、立方根
取整 ceil(), floor(), round() 向上/向下/四舍五入
三角函数 sin(), cos(), tan() 弧度制
随机数 random() [0.0, 1.0)

5.3 代码示例与陷阱

 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class MathDemo {
    
    public static void main(String[] args) {
        // ========== 基础运算 ==========
        int max = Math.max(10, 20);        // 20
        int min = Math.min(10, 20);        // 10
        int abs = Math.abs(-10);           // 10
        
        double sqrt = Math.sqrt(25);       // 5.0
        double pow = Math.pow(2, 10);      // 1024.0
        double cbrt = Math.cbrt(27);       // 3.0
        
        // ========== 取整运算(重要)==========
        double num = 3.7;
        
        System.out.println(Math.ceil(num));   // 4.0(向上取整)
        System.out.println(Math.floor(num));  // 3.0(向下取整)
        System.out.println(Math.round(num));  // 4(四舍五入,返回 long)
        
        // 负数取整
        System.out.println(Math.ceil(-3.7));   // -3.0
        System.out.println(Math.floor(-3.7));  // -4.0
        System.out.println(Math.round(-3.7));  // -4
        
        // ========== 精度陷阱 ==========
        // 陷阱1:浮点数精度丢失
        System.out.println(0.1 + 0.2);  // 0.30000000000000004
        System.out.println(Math.pow(0.1, 2));  // 0.010000000000000002
        
        // 金融计算必须使用 BigDecimal
        BigDecimal a = new BigDecimal("0.1");
        BigDecimal b = new BigDecimal("0.2");
        System.out.println(a.add(b));  // 0.3(精确)
        
        // 陷阱2:整数溢出
        int big = Integer.MAX_VALUE;  // 2147483647
        System.out.println(Math.abs(big));  // 2147483647
        System.out.println(Math.abs(Integer.MIN_VALUE));  // -2147483648(溢出!)
        
        // 陷阱3:Math.round 返回类型
        long rounded = Math.round(3.7);  // 返回 long,不是 int!
        
        // ========== 随机数 ==========
        // Math.random():生成 [0.0, 1.0)
        double random = Math.random();
        
        // 生成 [min, max] 范围的整数
        int minRange = 10, maxRange = 50;
        int randomInt = minRange + (int)(Math.random() * (maxRange - minRange + 1));
        
        // 更好的选择:ThreadLocalRandom(Java 7+)
        int betterRandom = ThreadLocalRandom.current().nextInt(minRange, maxRange + 1);
    }
}

5.4 Math vs StrictMath vs BigDecimal

 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
29
import java.math.BigDecimal;
import java.math.RoundingMode;

public class MathComparison {
    
    public static void main(String[] args) {
        // Math:本地实现,性能高,精度有限
        double mathResult = Math.sin(Math.PI / 2);  // 1.0
        
        // StrictMath:严格遵循 IEEE 754,跨平台结果一致
        double strictResult = StrictMath.sin(Math.PI / 2);  // 1.0
        
        // 区别:某些边界值处理可能不同
        
        // BigDecimal:精确计算,适合金融场景
        BigDecimal price = new BigDecimal("19.99");
        BigDecimal quantity = new BigDecimal("3");
        BigDecimal total = price.multiply(quantity);  // 59.97
        
        // 设置精度和舍入模式
        BigDecimal divisor = new BigDecimal("3");
        BigDecimal result = total.divide(divisor, 2, RoundingMode.HALF_UP);  // 19.99
        
        // 选择建议:
        // - 一般计算:Math
        // - 跨平台一致性:StrictMath
        // - 金融/精确计算:BigDecimal
    }
}

六、Stream API

6.1 核心定位

Stream API(Java 8+)提供函数式数据处理方式,支持链式操作、惰性求值和并行处理。

6.2 核心概念

概念 说明 示例
创建流 从集合、数组、IO 等创建 list.stream(), Arrays.stream(arr)
中间操作 返回新流,惰性执行 filter(), map(), sorted()
终止操作 触发执行,返回结果 collect(), forEach(), reduce()
短路操作 提前结束,提高效率 findFirst(), anyMatch(), limit()

6.3 创建流的多种方式

 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import java.util.*;
import java.util.stream.*;
import java.nio.file.*;
import java.io.IOException;

public class StreamCreation {
    
    public static void main(String[] args) throws IOException {
        // ========== 从集合创建 ==========
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        Stream<Integer> listStream = list.stream();           // 串行流
        Stream<Integer> parallelStream = list.parallelStream();  // 并行流
        
        Set<String> set = new HashSet<>(Arrays.asList("a", "b", "c"));
        Stream<String> setStream = set.stream();
        
        // Map 需要转换为 Entry 集合
        Map<String, Integer> map = Map.of("a", 1, "b", 2);
        Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();
        Stream<String> keyStream = map.keySet().stream();
        Stream<Integer> valueStream = map.values().stream();
        
        // ========== 从数组创建 ==========
        int[] intArray = {1, 2, 3, 4, 5};
        IntStream intStream = Arrays.stream(intArray);
        
        String[] strArray = {"a", "b", "c"};
        Stream<String> strStream = Arrays.stream(strArray);
        Stream<String> ofStream = Stream.of(strArray);
        
        // 基本类型流(避免装箱开销)
        IntStream intStream2 = IntStream.of(1, 2, 3);
        LongStream longStream = LongStream.of(1L, 2L, 3L);
        DoubleStream doubleStream = DoubleStream.of(1.0, 2.0, 3.0);
        
        // ========== 从 IO 创建 ==========
        // 文件行流(必须 try-with-resources)
        try (Stream<String> lines = Files.lines(Paths.get("file.txt"))) {
            lines.forEach(System.out::println);
        }
        
        // ========== 生成流 ==========
        // 无限流:需要配合 limit()
        Stream<Integer> infinite = Stream.iterate(0, n -> n + 2);  // 0, 2, 4, 6...
        Stream<Integer> limited = Stream.iterate(0, n -> n < 10, n -> n + 2);  // 0, 2, 4, 6, 8
        
        // 随机数流
        Random random = new Random();
        Stream<Integer> randomStream = random.ints(1, 100).boxed();
        
        // 生成器
        Stream<String> generated = Stream.generate(() -> "hello").limit(5);
        
        // 空流
        Stream<String> empty = Stream.empty();
        
        // 拼接流
        Stream<String> concat = Stream.concat(Stream.of("a", "b"), Stream.of("c", "d"));
    }
}

6.4 中间操作详解

 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import java.util.*;
import java.util.stream.*;

public class StreamIntermediateOps {
    
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // ========== 过滤 ==========
        // filter:保留符合条件的元素
        List<Integer> evens = numbers.stream()
            .filter(n -> n % 2 == 0)
            .collect(Collectors.toList());  // [2, 4, 6, 8, 10]
        
        // distinct:去重(基于 equals/hashCode)
        List<Integer> distinct = Arrays.asList(1, 2, 2, 3, 3, 3).stream()
            .distinct()
            .collect(Collectors.toList());  // [1, 2, 3]
        
        // ========== 映射 ==========
        // map:一对一转换
        List<Integer> squares = numbers.stream()
            .map(n -> n * n)
            .collect(Collectors.toList());  // [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
        
        // flatMap:一对多展开(扁平化)
        List<List<Integer>> nested = Arrays.asList(
            Arrays.asList(1, 2),
            Arrays.asList(3, 4),
            Arrays.asList(5, 6)
        );
        List<Integer> flat = nested.stream()
            .flatMap(List::stream)
            .collect(Collectors.toList());  // [1, 2, 3, 4, 5, 6]
        
        // mapToInt 等:避免装箱
        int sum = numbers.stream()
            .mapToInt(Integer::intValue)
            .sum();
        
        // ========== 排序 ==========
        // sorted():自然排序(元素需实现 Comparable)
        List<String> sorted = Arrays.asList("banana", "apple", "cherry").stream()
            .sorted()
            .collect(Collectors.toList());  // [apple, banana, cherry]
        
        // sorted(Comparator):自定义排序
        List<String> reversed = Arrays.asList("a", "bb", "ccc").stream()
            .sorted(Comparator.comparingInt(String::length).reversed())
            .collect(Collectors.toList());  // [ccc, bb, a]
        
        // ========== 截断与跳过 ==========
        // limit:保留前 n 个
        List<Integer> first3 = numbers.stream()
            .limit(3)
            .collect(Collectors.toList());  // [1, 2, 3]
        
        // skip:跳过前 n 个
        List<Integer> skip3 = numbers.stream()
            .skip(3)
            .collect(Collectors.toList());  // [4, 5, 6, 7, 8, 9, 10]
        
        // 分页:skip + limit
        int pageSize = 3;
        int pageNum = 2;  // 第2页
        List<Integer> page = numbers.stream()
            .skip((long) (pageNum - 1) * pageSize)
            .limit(pageSize)
            .collect(Collectors.toList());  // [4, 5, 6]
        
        // ========== 窥视(调试)==========
        List<Integer> peeked = numbers.stream()
            .filter(n -> n > 5)
            .peek(n -> System.out.println("过滤后: " + n))  // 调试输出
            .map(n -> n * 2)
            .peek(n -> System.out.println("映射后: " + n))
            .collect(Collectors.toList());
    }
}

6.5 终止操作详解

 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import java.util.*;
import java.util.stream.*;

public class StreamTerminalOps {
    
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        
        // ========== 聚合操作 ==========
        // count:计数
        long count = numbers.stream().count();  // 5
        
        // sum/average/max/min(基本类型流)
        IntStream intStream = numbers.stream().mapToInt(Integer::intValue);
        int sum = intStream.sum();           // 15
        // 注意:sum() 后流已关闭,需要重新创建
        double avg = numbers.stream().mapToInt(Integer::intValue).average().orElse(0);  // 3.0
        int max = numbers.stream().mapToInt(Integer::intValue).max().orElse(0);  // 5
        
        // reduce:归约操作
        // 求和
        int total = numbers.stream()
            .reduce(0, Integer::sum);  // 15
        
        // 乘积(无初始值,返回 Optional)
        Optional<Integer> product = numbers.stream()
            .reduce((a, b) -> a * b);  // Optional[120]
        
        // 字符串拼接
        String joined = Stream.of("a", "b", "c")
            .reduce("", String::concat);  // "abc"
        
        // ========== 收集器 ==========
        // toList/toSet
        List<Integer> list = numbers.stream().collect(Collectors.toList());
        Set<Integer> set = numbers.stream().collect(Collectors.toSet());
        
        // toMap(注意处理 key 冲突)
        Map<Integer, String> map = numbers.stream()
            .collect(Collectors.toMap(
                n -> n,
                n -> "value" + n,
                (existing, replacement) -> existing  // 处理冲突
            ));
        
        // joining:字符串连接
        String csv = numbers.stream()
            .map(String::valueOf)
            .collect(Collectors.joining(", "));  // "1, 2, 3, 4, 5"
        
        // groupingBy:分组
        Map<String, List<Integer>> grouped = numbers.stream()
            .collect(Collectors.groupingBy(n -> n % 2 == 0 ? "偶数" : "奇数"));
        // {奇数=[1, 3, 5], 偶数=[2, 4]}
        
        // partitioningBy:分区(true/false 两组)
        Map<Boolean, List<Integer>> partitioned = numbers.stream()
            .collect(Collectors.partitioningBy(n -> n > 3));
        // {false=[1, 2, 3], true=[4, 5]}
        
        // summarizingInt:统计信息
        IntSummaryStatistics stats = numbers.stream()
            .collect(Collectors.summarizingInt(Integer::intValue));
        System.out.println("Count: " + stats.getCount());    // 5
        System.out.println("Sum: " + stats.getSum());        // 15
        System.out.println("Min: " + stats.getMin());        // 1
        System.out.println("Max: " + stats.getMax());        // 5
        System.out.println("Avg: " + stats.getAverage());    // 3.0
        
        // ========== 查找与匹配 ==========
        // findFirst:返回第一个(串行流通常更快)
        Optional<Integer> first = numbers.stream()
            .filter(n -> n > 3)
            .findFirst();  // Optional[4]
        
        // findAny:返回任意一个(并行流更快)
        Optional<Integer> any = numbers.parallelStream()
            .filter(n -> n > 3)
            .findAny();
        
        // anyMatch:是否存在匹配
        boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0);  // true
        
        // allMatch:是否全部匹配
        boolean allPositive = numbers.stream().allMatch(n -> n > 0);  // true
        
        // noneMatch:是否都不匹配
        boolean noNegative = numbers.stream().noneMatch(n -> n < 0);  // true
        
        // ========== 遍历 ==========
        // forEach:遍历每个元素(无返回值)
        numbers.stream().forEach(System.out::println);
        
        // forEachOrdered:保证顺序(并行流中有序)
        numbers.parallelStream().forEachOrdered(System.out::println);
    }
}

6.6 性能陷阱与最佳实践

 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import java.util.*;
import java.util.stream.*;

public class StreamBestPractice {
    
    // 陷阱1:错误的数据源选择
    public void badDataSource() {
        // 错误:Stream.iterate 不适合 limit
        Stream.iterate(0, n -> n + 1)  // 无限流
              .limit(100)              // 截断
              .collect(Collectors.toList());
        
        // 正确:IntStream.range
        IntStream.range(0, 100).boxed().collect(Collectors.toList());
    }
    
    // 陷阱2:多次遍历流
    public void multipleIteration() {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
        
        long count = stream.count();  // 流已关闭
        // int sum = stream.mapToInt(Integer::intValue).sum();  // IllegalStateException!
        
        // 正确做法:收集到集合后操作
        List<Integer> list = Stream.of(1, 2, 3, 4, 5).collect(Collectors.toList());
        long count2 = list.size();
        int sum = list.stream().mapToInt(Integer::intValue).sum();
    }
    
    // 陷阱3:并行流误用
    public void parallelPitfall() {
        List<Integer> smallList = Arrays.asList(1, 2, 3);
        
        // 错误:小数据量使用并行流( overhead > 收益)
        smallList.parallelStream().map(n -> n * n).collect(Collectors.toList());
        
        // 错误:数据源不支持高效拆分
        Stream.iterate(0, n -> n + 1).limit(1000).parallel();  // 性能差
        
        // 正确:大数据量 + 高效数据源(IntStream, Arrays.stream, IntStream.range)
        IntStream.range(0, 1_000_000).parallel().sum();
    }
    
    // 最佳实践1:优先使用基本类型流
    public void primitiveStream() {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        
        // 有装箱开销
        int sum1 = list.stream().reduce(0, Integer::sum);
        
        // 无装箱开销(推荐)
        int sum2 = list.stream().mapToInt(Integer::intValue).sum();
    }
    
    // 最佳实践2:使用适当的数据结构
    public void rightDataStructure() {
        // 频繁 contains 检查:用 Set 而非 List
        Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
        boolean exists = set.contains(3);  // O(1)
        
        // 频繁随机访问:用 ArrayList 而非 LinkedList
        List<Integer> arrayList = new ArrayList<>(Arrays.asList(1, 2, 3));
        int element = arrayList.get(2);  // O(1)
    }
    
    // 最佳实践3:短路操作提前终止
    public void shortCircuit() {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // 找到第一个偶数后立即停止(只检查 1, 2)
        Optional<Integer> firstEven = list.stream()
            .filter(n -> {
                System.out.println("检查: " + n);
                return n % 2 == 0;
            })
            .findFirst();  // 短路操作
    }
}

七、StringUtils(Apache Commons Lang)

7.1 核心定位

Apache Commons Lang 的 StringUtils 提供更强大的字符串操作,比原生 Java 更空安全、更简洁。

7.2 与原生 Java 对比

操作 原生 Java StringUtils 优势
判空 str == null || str.isEmpty() StringUtils.isEmpty(str) 简洁、可读
判空白 str == null || str.trim().isEmpty() StringUtils.isBlank(str) 包含全空白判断
默认值 str != null ? str : default StringUtils.defaultString(str) 简洁
截取 手动检查边界 StringUtils.substring(str, 0, 5) 安全(不抛异常)
分割 split()(正则) split()(按字符) 性能更好

7.3 代码示例

  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
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import org.apache.commons.lang3.StringUtils;

public class StringUtilsDemo {
    
    public static void main(String[] args) {
        String str = null;
        String empty = "";
        String blank = "   ";
        String text = "  Hello World  ";
        
        // ========== 空值判断 ==========
        // isEmpty:null 或空字符串
        System.out.println(StringUtils.isEmpty(str));    // true
        System.out.println(StringUtils.isEmpty(empty));  // true
        System.out.println(StringUtils.isEmpty(blank));  // false(空白字符不算空)
        
        // isBlank:null、空字符串或仅空白字符
        System.out.println(StringUtils.isBlank(str));    // true
        System.out.println(StringUtils.isBlank(empty));  // true
        System.out.println(StringUtils.isBlank(blank));  // true
        
        // ========== 默认值 ==========
        // defaultString:null 转为空字符串
        String safe = StringUtils.defaultString(str);           // ""
        String withDefault = StringUtils.defaultString(str, "N/A");  // "N/A"
        
        // defaultIfBlank:blank 时返回默认值
        String ifBlank = StringUtils.defaultIfBlank(blank, "default");  // "default"
        
        // ========== 截取(安全)==========
        String longStr = "Hello";
        
        // 原生 substring 可能抛 IndexOutOfBoundsException
        // longStr.substring(0, 100);  // 异常!
        
        // StringUtils 安全截取(超出范围不抛异常)
        String safeSub = StringUtils.substring(longStr, 0, 100);  // "Hello"
        String fromIndex = StringUtils.substring(longStr, 2);     // "llo"
        String between = StringUtils.substringBetween("[abc]", "[", "]");  // "abc"
        
        // ========== 分割(按字符,非正则)==========
        String csv = "a,b,c";
        
        // 原生 split 使用正则(性能较差)
        String[] nativeSplit = csv.split(",");
        
        // StringUtils 按字符分割(性能更好)
        String[] apacheSplit = StringUtils.split(csv, ",");
        
        // 限制分割次数
        String[] limited = StringUtils.split("a,b,c,d", ",", 2);  // ["a", "b,c,d"]
        
        // ========== 连接 ==========
        List<String> list = Arrays.asList("a", "b", "c");
        
        // join:连接集合/数组
        String joined = StringUtils.join(list, ", ");  // "a, b, c"
        String arrayJoined = StringUtils.join(new String[]{"a", "b", "c"}, "-");  // "a-b-c"
        
        // ========== 删除与替换 ==========
        String withSpaces = "  Hello  World  ";
        
        // trim:删除首尾空格(同原生)
        String trimmed = StringUtils.trim(withSpaces);  // "Hello  World"
        
        // deleteWhitespace:删除所有空格
        String noSpace = StringUtils.deleteWhitespace(withSpaces);  // "HelloWorld"
        
        // remove:删除指定子串
        String removed = StringUtils.remove("Hello World", "o");  // "Hell Wrld"
        
        // replace:替换(null 安全)
        String replaced = StringUtils.replace(null, "a", "b");  // null(不抛异常)
        
        // ========== 包含与定位 ==========
        String content = "Hello World";
        
        // contains:null 安全
        System.out.println(StringUtils.contains(content, "World"));  // true
        System.out.println(StringUtils.contains(null, "a"));         // false
        
        // 忽略大小写
        System.out.println(StringUtils.containsIgnoreCase(content, "world"));  // true
        
        // 计数
        int count = StringUtils.countMatches("aaa", "a");  // 3
        
        // 索引(null 安全)
        int index = StringUtils.indexOf(content, "o");      // 4
        int lastIndex = StringUtils.lastIndexOf(content, "o");  // 7
        
        // ========== 填充与对齐 ==========
        // leftPad:左侧填充
        String leftPadded = StringUtils.leftPad("5", 3, "0");  // "005"
        
        // rightPad:右侧填充
        String rightPadded = StringUtils.rightPad("Hi", 5, "!");  // "Hi!!!"
        
        // center:居中
        String centered = StringUtils.center("Hi", 6, "*");  // "**Hi**"
        
        // repeat:重复
        String repeated = StringUtils.repeat("ab", 3);  // "ababab"
        
        // ========== 大小写转换 ==========
        String mixed = "hello WORLD";
        
        System.out.println(StringUtils.upperCase(mixed));   // "HELLO WORLD"
        System.out.println(StringUtils.lowerCase(mixed));   // "hello world"
        System.out.println(StringUtils.capitalize("hello")); // "Hello"
        System.out.println(StringUtils.uncapitalize("Hello")); // "hello"
        
        // swapCase:大小写互换
        System.out.println(StringUtils.swapCase("Hello"));  // "hELLO"
    }
}

7.4 Maven 依赖

1
2
3
4
5
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

八、工具类选择速查表

场景 推荐工具类 说明
数组排序/查找 Arrays 双轴快排、二分查找
集合排序/同步 Collections 线程安全包装、不可变视图
空值处理 Objects 空安全比较、参数校验
文件读写 Files/Paths NIO.2,支持 Stream
数学计算 Math / BigDecimal 基本运算 / 精确计算
数据处理 Stream API 函数式、链式操作
字符串处理 StringUtils Apache Commons,更强大

九、总结

核心要点

  1. Arrays:掌握 asList() 的陷阱,优先使用 copyOf() 进行数组扩容
  2. Collections:理解同步包装与并发集合的区别,善用不可变集合
  3. Objects:防御式编程的基础,所有参数校验都应使用 requireNonNull()
  4. Files/Paths:NIO.2 是现代 Java 文件操作的标准,务必掌握 try-with-resources
  5. Math:金融计算必须使用 BigDecimal,注意浮点数精度问题
  6. Stream:理解惰性求值和短路操作,避免并行流误用
  7. StringUtils:项目中引入 Apache Commons Lang 可大幅提升字符串处理效率

性能建议

  • 小数据量:直接使用循环,避免 Stream overhead
  • 大数据量:使用 Stream 并行处理,但确保数据源可高效拆分
  • 频繁操作:优先使用基本类型流(IntStream 等)避免装箱
  • 字符串处理:大量操作时,Apache Commons Lang 性能优于原生方法