【JDK8新特性】新工具类与API改进Day11


一、写在前面

欢迎来到JDK8新特性教学系列的Day11

在前面的文章中,我们已经深入学习了Lambda表达式、Stream流、Optional、日期时间API、CompletableFuture等核心特性。今天,我们将聚焦于JDK8中那些小而美的工具类和API改进------它们虽然不如Stream或Lambda那样引人注目,但在日常开发中却能极大地提升代码质量和开发效率。

本文学习目标:

  • 掌握Objects工具类的使用,告别空指针烦恼
  • 了解String、Arrays、Collections的新方法
  • 熟练使用Map接口的函数式操作方法
  • 理解反射API的改进和Base64编码的使用

文章目录

    • 一、写在前面
    • 二、Objects类
      • [2.1 isNull / nonNull](#2.1 isNull / nonNull)
      • [2.2 requireNonNull](#2.2 requireNonNull)
      • [2.3 equals / deepEquals](#2.3 equals / deepEquals)
      • [2.4 hash / hashCode](#2.4 hash / hashCode)
      • [2.5 toString](#2.5 toString)
      • [2.6 compare](#2.6 compare)
    • 三、String类的新方法
      • [3.1 join方法(JDK8)](#3.1 join方法(JDK8))
      • [3.2 chars方法(JDK8)](#3.2 chars方法(JDK8))
      • [3.3 codePoints方法(JDK8)](#3.3 codePoints方法(JDK8))
      • [3.4 lines方法(JDK11)](#3.4 lines方法(JDK11))
      • [3.5 isBlank / strip / repeat(JDK11)](#3.5 isBlank / strip / repeat(JDK11))
    • 四、Arrays类的新方法
      • [4.1 parallelSort](#4.1 parallelSort)
      • [4.2 setAll / parallelSetAll](#4.2 setAll / parallelSetAll)
      • [4.3 parallelPrefix](#4.3 parallelPrefix)
      • [4.4 spliterator](#4.4 spliterator)
    • 五、Collections类的新方法
      • [5.1 sort方法](#5.1 sort方法)
      • [5.2 replaceAll方法](#5.2 replaceAll方法)
      • [5.3 其他实用方法](#5.3 其他实用方法)
    • 六、Map接口的新方法
      • [6.1 forEach](#6.1 forEach)
      • [6.2 getOrDefault](#6.2 getOrDefault)
      • [6.3 putIfAbsent / computeIfAbsent](#6.3 putIfAbsent / computeIfAbsent)
      • [6.4 compute / computeIfPresent](#6.4 compute / computeIfPresent)
      • [6.5 merge](#6.5 merge)
      • [6.6 replace / replaceAll](#6.6 replace / replaceAll)
      • [6.7 remove(key, value)](#6.7 remove(key, value))
    • 七、反射API的改进
      • [7.1 获取参数名](#7.1 获取参数名)
      • [7.2 类型注解(Type Annotations)](#7.2 类型注解(Type Annotations))
      • [7.3 重复注解](#7.3 重复注解)
    • 八、Base64编码
      • [8.1 基本使用](#8.1 基本使用)
      • [8.2 URL安全的Base64](#8.2 URL安全的Base64)
      • [8.3 MIME格式的Base64](#8.3 MIME格式的Base64)
      • [8.4 流式处理](#8.4 流式处理)
      • [8.5 实际应用场景](#8.5 实际应用场景)
    • 九、踩坑提醒
      • [9.1 Objects.requireNonNull的异常信息](#9.1 Objects.requireNonNull的异常信息)
      • [9.2 Map.compute的并发问题](#9.2 Map.compute的并发问题)
      • [9.3 Map.compute返回null会删除key](#9.3 Map.compute返回null会删除key)
      • [9.4 Arrays.parallelSort的适用场景](#9.4 Arrays.parallelSort的适用场景)
      • [9.5 Base64编码后的数据膨胀](#9.5 Base64编码后的数据膨胀)
    • 十、面试高频考点
      • [Q1: Objects.equals和==的区别?](#Q1: Objects.equals和==的区别?)
      • [Q2: Map.merge的作用?](#Q2: Map.merge的作用?)
      • [Q3: computeIfAbsent和putIfAbsent的区别?](#Q3: computeIfAbsent和putIfAbsent的区别?)
      • [Q4: JDK8反射获取参数名需要什么条件?](#Q4: JDK8反射获取参数名需要什么条件?)
      • [Q5: Base64.getEncoder()和getUrlEncoder()的区别?](#Q5: Base64.getEncoder()和getUrlEncoder()的区别?)
    • 十一、总结
    • 十二、下一步预告
    • 十三、参考资料
    • 十四、互动话题

二、Objects类

java.util.Objects类是JDK7引入的,但在JDK8中得到了进一步增强。它提供了一系列静态方法来处理对象,避免繁琐的null检查。

2.1 isNull / nonNull

java 复制代码
import java.util.Objects;

public class ObjectsDemo {
    public static void main(String[] args) {
        String str = null;
        String text = "Hello";
        
        // isNull: 判断对象是否为null
        System.out.println(Objects.isNull(str));     // true
        System.out.println(Objects.isNull(text));    // false
        
        // nonNull: 判断对象是否不为null
        System.out.println(Objects.nonNull(str));    // false
        System.out.println(Objects.nonNull(text));   // true
        
        // 常用于Stream过滤
        List<String> list = Arrays.asList("a", null, "b", null, "c");
        List<String> nonNullList = list.stream()
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
        System.out.println(nonNullList); // [a, b, c]
    }
}

2.2 requireNonNull

java 复制代码
public class RequireNonNullDemo {
    
    // 传统写法
    public void setNameOld(String name) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        this.name = name;
    }
    
    // Objects.requireNonNull写法
    public void setName(String name) {
        this.name = Objects.requireNonNull(name);
    }
    
    // 带自定义异常信息
    public void setNameWithMessage(String name) {
        this.name = Objects.requireNonNull(name, "name cannot be null");
    }
    
    // 带异常信息生成器(延迟计算,仅在需要时执行)
    public void setNameWithSupplier(String name) {
        this.name = Objects.requireNonNull(name, () -> "name cannot be null, current time: " + System.currentTimeMillis());
    }
}

2.3 equals / deepEquals

java 复制代码
public class EqualsDemo {
    public static void main(String[] args) {
        String a = "hello";
        String b = "hello";
        String c = null;
        
        // Objects.equals: 安全的equals比较,自动处理null
        System.out.println(Objects.equals(a, b)); // true
        System.out.println(Objects.equals(a, c)); // false
        System.out.println(Objects.equals(c, c)); // true (两个null相等)
        
        // 对比传统写法
        // 传统:if (a != null && a.equals(b)) ...
        // 现在:if (Objects.equals(a, b)) ...
        
        // deepEquals: 用于数组的深层比较
        int[] arr1 = {1, 2, 3};
        int[] arr2 = {1, 2, 3};
        System.out.println(Objects.equals(arr1, arr2));     // false (比较引用)
        System.out.println(Objects.deepEquals(arr1, arr2)); // true (比较内容)
        
        // 多维数组
        int[][] deep1 = {{1, 2}, {3, 4}};
        int[][] deep2 = {{1, 2}, {3, 4}};
        System.out.println(Objects.deepEquals(deep1, deep2)); // true
    }
}

2.4 hash / hashCode

java 复制代码
public class HashDemo {
    public static void main(String[] args) {
        // 计算多个对象的组合hashCode
        String name = "Alice";
        Integer age = 25;
        String email = "alice@example.com";
        
        int hash = Objects.hash(name, age, email);
        System.out.println("Combined hash: " + hash);
        
        // 在实体类中的典型应用
        public class User {
            private String name;
            private Integer age;
            private String email;
            
            @Override
            public int hashCode() {
                return Objects.hash(name, age, email);
            }
            
            @Override
            public boolean equals(Object obj) {
                if (this == obj) return true;
                if (obj == null || getClass() != obj.getClass()) return false;
                User user = (User) obj;
                return Objects.equals(name, user.name) &&
                       Objects.equals(age, user.age) &&
                       Objects.equals(email, user.email);
            }
        }
    }
}

2.5 toString

java 复制代码
public class ToStringDemo {
    public static void main(String[] args) {
        String str = "hello";
        String nullStr = null;
        
        // Objects.toString: 安全的toString,可指定null时的默认值
        System.out.println(Objects.toString(str));              // hello
        System.out.println(Objects.toString(nullStr));          // null
        System.out.println(Objects.toString(nullStr, "N/A"));   // N/A
    }
}

2.6 compare

java 复制代码
public class CompareDemo {
    public static void main(String[] args) {
        Integer a = 10;
        Integer b = 20;
        
        // Objects.compare: 使用Comparator比较两个对象
        int result = Objects.compare(a, b, Comparator.naturalOrder());
        System.out.println(result); // -1 (a < b)
        
        // 处理null的情况
        Integer c = null;
        // Objects.compare(null, b, Comparator) 会抛出NullPointerException
        // 需要确保比较器能处理null
        int result2 = Objects.compare(c, b, Comparator.nullsFirst(Comparator.naturalOrder()));
        System.out.println(result2); // -1 (null < 任何值)
    }
}

三、String类的新方法

JDK8为String类添加了一些实用方法,JDK11又进一步增强。以下是JDK8及之后版本的重要新增方法。

3.1 join方法(JDK8)

java 复制代码
public class StringJoinDemo {
    public static void main(String[] args) {
        // String.join: 使用分隔符连接多个字符串
        String result = String.join("-", "2024", "05", "20");
        System.out.println(result); // 2024-05-20
        
        // 连接Iterable
        List<String> fruits = Arrays.asList("apple", "banana", "cherry");
        String fruitStr = String.join(", ", fruits);
        System.out.println(fruitStr); // apple, banana, cherry
        
        // 对比StringJoiner(更灵活)
        StringJoiner joiner = new StringJoiner(", ", "[", "]");
        joiner.add("apple").add("banana").add("cherry");
        System.out.println(joiner.toString()); // [apple, banana, cherry]
    }
}

3.2 chars方法(JDK8)

java 复制代码
public class StringCharsDemo {
    public static void main(String[] args) {
        String text = "Hello";
        
        // chars(): 返回IntStream,包含字符串中每个字符的Unicode值
        text.chars()
            .forEach(c -> System.out.print(c + " "));
        // 输出: 72 101 108 108 111
        
        // 统计元音字母数量
        long vowelCount = text.toLowerCase().chars()
            .filter(c -> "aeiou".indexOf(c) != -1)
            .count();
        System.out.println("元音数量: " + vowelCount); // 2
        
        // 转换为字符列表
        List<Character> charList = text.chars()
            .mapToObj(c -> (char) c)
            .collect(Collectors.toList());
        System.out.println(charList); // [H, e, l, l, o]
    }
}

3.3 codePoints方法(JDK8)

java 复制代码
public class CodePointsDemo {
    public static void main(String[] args) {
        // codePoints(): 返回IntStream,正确处理Unicode增补字符
        String emoji = "Hello 👋";
        
        // chars() 会把增补字符拆成两个int
        System.out.println("chars count: " + emoji.chars().count()); // 8
        
        // codePoints() 正确处理Unicode码点
        System.out.println("codePoints count: " + emoji.codePoints().count()); // 7
        
        // 过滤出所有emoji
        String text = "Hello 👋 World 🌍!";
        List<String> emojis = text.codePoints()
            .filter(Character::isSupplementaryCodePoint)
            .mapToObj(Character::toChars)
            .map(String::new)
            .collect(Collectors.toList());
        System.out.println(emojis); // [👋, 🌍]
    }
}

3.4 lines方法(JDK11)

java 复制代码
public class StringLinesDemo {
    public static void main(String[] args) {
        String multiline = "Line 1\nLine 2\r\nLine 3\rLine 4";
        
        // lines(): 按行分割字符串,返回Stream<String>
        multiline.lines()
            .forEach(System.out::println);
        // 输出:
        // Line 1
        // Line 2
        // Line 3
        // Line 4
        
        // 统计非空行数
        long nonEmptyLines = multiline.lines()
            .filter(line -> !line.trim().isEmpty())
            .count();
        
        // 读取文件并处理每一行
        Files.lines(Path.of("data.txt")) // 返回 Stream<String>
            .flatMap(line -> line.lines()) // 处理每行中的多行文本
            .forEach(System.out::println);
    }
}

3.5 isBlank / strip / repeat(JDK11)

java 复制代码
public class StringJDK11Demo {
    public static void main(String[] args) {
        // isBlank(): 判断字符串是否为空或仅包含空白字符
        System.out.println("".isBlank());           // true
        System.out.println("   ".isBlank());       // true
        System.out.println("hello".isBlank());     // false
        
        // 对比isEmpty()
        System.out.println("   ".isEmpty());       // false
        
        // strip(): 去除首尾Unicode空白字符(比trim更全面)
        String text = "\u2003 hello \u2003"; // 使用em空格
        System.out.println("[" + text.trim() + "]");    // [ hello ]
        System.out.println("[" + text.strip() + "]");   // [hello]
        
        // stripLeading(): 仅去除开头空白
        // stripTrailing(): 仅去除结尾空白
        
        // repeat(): 重复字符串
        System.out.println("-".repeat(20)); // --------------------
        System.out.println("Na".repeat(5) + " Batman!"); // NaNaNaNaNa Batman!
    }
}

四、Arrays类的新方法

JDK8为java.util.Arrays类添加了多个支持并行操作的方法。

4.1 parallelSort

java 复制代码
public class ParallelSortDemo {
    public static void main(String[] args) {
        int[] arr = {5, 2, 8, 1, 9, 3, 7, 4, 6};
        
        // parallelSort: 并行排序,大数据量时更快
        Arrays.parallelSort(arr);
        System.out.println(Arrays.toString(arr)); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
        
        // 部分排序
        int[] arr2 = {5, 2, 8, 1, 9, 3, 7, 4, 6};
        Arrays.parallelSort(arr2, 2, 7); // 排序索引2到6的元素
        System.out.println(Arrays.toString(arr2)); // [5, 2, 1, 3, 7, 8, 9, 4, 6]
        
        // 对象数组(需要实现Comparable或使用Comparator)
        String[] words = {"cherry", "apple", "banana"};
        Arrays.parallelSort(words, String.CASE_INSENSITIVE_ORDER);
    }
}

4.2 setAll / parallelSetAll

java 复制代码
public class SetAllDemo {
    public static void main(String[] args) {
        int[] arr = new int[10];
        
        // setAll: 使用IntUnaryOperator设置每个元素
        Arrays.setAll(arr, i -> i * i); // 设置每个元素为其索引的平方
        System.out.println(Arrays.toString(arr)); // [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
        
        // parallelSetAll: 并行版本
        int[] arr2 = new int[1000000];
        Arrays.parallelSetAll(arr2, i -> i * 2);
        
        // 斐波那契数列
        long[] fib = new long[20];
        Arrays.setAll(fib, i -> i < 2 ? 1 : fib[i - 1] + fib[i - 2]);
        System.out.println(Arrays.toString(fib));
        // [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
    }
}

4.3 parallelPrefix

java 复制代码
public class ParallelPrefixDemo {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        
        // parallelPrefix: 并行前缀计算(累积计算)
        // 每个元素 = 前面所有元素的累积结果
        Arrays.parallelPrefix(arr, (a, b) -> a + b);
        System.out.println(Arrays.toString(arr)); // [1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
        
        // 阶乘计算
        int[] factorial = {1, 2, 3, 4, 5, 6, 7, 8};
        Arrays.parallelPrefix(factorial, (a, b) -> a * b);
        System.out.println(Arrays.toString(factorial)); // [1, 2, 6, 24, 120, 720, 5040, 40320]
        
        // 部分前缀计算
        int[] arr2 = {1, 2, 3, 4, 5, 6, 7, 8};
        Arrays.parallelPrefix(arr2, 2, 6, (a, b) -> a + b);
        System.out.println(Arrays.toString(arr2)); // [1, 2, 3, 7, 12, 18, 7, 8]
    }
}

4.4 spliterator

java 复制代码
public class ArraysSpliteratorDemo {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        
        // spliterator: 返回数组的Spliterator,用于并行遍历
        Spliterator.OfInt spliterator = Arrays.spliterator(arr);
        
        // 使用tryAdvance遍历
        spliterator.tryAdvance(System.out::println); // 1
        
        // 使用forEachRemaining遍历剩余元素
        spliterator.forEachRemaining(System.out::print); // 2345678910
        
        // 获取带特征的Spliterator
        Spliterator.OfInt spliterator2 = Arrays.spliterator(arr, 2, 8);
        System.out.println("\n估计大小: " + spliterator2.estimateSize()); // 6
        System.out.println("特征: " + spliterator2.characteristics());
    }
}

五、Collections类的新方法

5.1 sort方法

java 复制代码
public class CollectionsSortDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>(Arrays.asList("cherry", "apple", "banana"));
        
        // Collections.sort: 传统排序方法
        Collections.sort(list);
        System.out.println(list); // [apple, banana, cherry]
        
        // 使用Comparator
        Collections.sort(list, Collections.reverseOrder());
        System.out.println(list); // [cherry, banana, apple]
        
        // 注意:List接口在JDK8中新增了默认方法sort
        list.sort(Comparator.naturalOrder()); // 更简洁的写法
    }
}

5.2 replaceAll方法

java 复制代码
public class CollectionsReplaceAllDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>(Arrays.asList("a", "b", "a", "c", "a"));
        
        // replaceAll: 替换所有匹配的元素
        Collections.replaceAll(list, "a", "x");
        System.out.println(list); // [x, b, x, c, x]
        
        // 对比List.replaceAll (JDK8新增,使用UnaryOperator)
        list.replaceAll(s -> s.toUpperCase());
        System.out.println(list); // [X, B, X, C, X]
    }
}

5.3 其他实用方法

java 复制代码
public class CollectionsOtherMethods {
    public static void main(String[] args) {
        // emptyIterator / emptyListIterator
        Iterator<String> emptyIter = Collections.emptyIterator();
        
        // singletonList / singletonMap / singleton
        // 返回不可变的单元素集合
        List<String> single = Collections.singletonList("only");
        // single.add("another"); // 抛出UnsupportedOperationException
        
        // checkedCollection / checkedList / checkedMap
        // 返回类型安全的视图
        List rawList = new ArrayList();
        List<String> checkedList = Collections.checkedList(rawList, String.class);
        // checkedList.add(123); // 抛出ClassCastException
    }
}

六、Map接口的新方法

JDK8为Map接口添加了大量默认方法,使其支持函数式编程。

6.1 forEach

java 复制代码
public class MapForEachDemo {
    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<>();
        scores.put("Alice", 90);
        scores.put("Bob", 85);
        scores.put("Charlie", 95);
        
        // 传统遍历
        for (Map.Entry<String, Integer> entry : scores.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
        
        // forEach: 更简洁的遍历
        scores.forEach((name, score) -> System.out.println(name + ": " + score));
        
        // 带条件的遍历
        scores.forEach((name, score) -> {
            if (score >= 90) {
                System.out.println(name + " 获得优秀!");
            }
        });
    }
}

6.2 getOrDefault

java 复制代码
public class MapGetOrDefaultDemo {
    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<>();
        scores.put("Alice", 90);
        
        // 传统写法
        Integer bobScore = scores.get("Bob");
        if (bobScore == null) {
            bobScore = 0;
        }
        
        // getOrDefault: 简洁的默认值处理
        Integer aliceScore = scores.getOrDefault("Alice", 0); // 90
        Integer bobScore2 = scores.getOrDefault("Bob", 0);    // 0
        
        // 注意:getOrDefault不会将默认值放入Map
        System.out.println(scores); // {Alice=90}
    }
}

6.3 putIfAbsent / computeIfAbsent

java 复制代码
public class MapPutIfAbsentDemo {
    public static void main(String[] args) {
        Map<String, List<String>> groups = new HashMap<>();
        
        // 传统写法:分组存储
        String key = "developers";
        List<String> list = groups.get(key);
        if (list == null) {
            list = new ArrayList<>();
            groups.put(key, list);
        }
        list.add("Alice");
        
        // putIfAbsent: 如果不存在则放入
        groups.putIfAbsent("testers", new ArrayList<>());
        groups.get("testers").add("Bob");
        
        // computeIfAbsent: 更优雅的分组写法(推荐)
        groups.computeIfAbsent("managers", k -> new ArrayList<>()).add("Charlie");
        
        // computeIfAbsent只在key不存在时计算value
        // 适用于创建成本较高的对象
        Map<String, ExpensiveObject> cache = new HashMap<>();
        ExpensiveObject obj = cache.computeIfAbsent("key", k -> createExpensiveObject(k));
    }
    
    private static ExpensiveObject createExpensiveObject(String key) {
        // 耗时操作
        return new ExpensiveObject(key);
    }
}

6.4 compute / computeIfPresent

java 复制代码
public class MapComputeDemo {
    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<>();
        scores.put("Alice", 90);
        
        // compute: 计算新值(无论key是否存在)
        // 参数:key, BiFunction(oldValue, key) -> newValue
        scores.compute("Alice", (k, v) -> v == null ? 100 : v + 10);
        System.out.println(scores); // {Alice=100}
        
        scores.compute("Bob", (k, v) -> v == null ? 50 : v + 10);
        System.out.println(scores); // {Alice=100, Bob=50}
        
        // computeIfPresent: 只在key存在时计算
        scores.computeIfPresent("Alice", (k, v) -> v * 2); // Alice=200
        scores.computeIfPresent("Charlie", (k, v) -> v * 2); // 不执行,Charlie不存在
        
        // 返回null会删除key
        scores.compute("Bob", (k, v) -> null); // Bob被删除
        System.out.println(scores); // {Alice=200}
    }
}

6.5 merge

java 复制代码
public class MapMergeDemo {
    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<>();
        scores.put("Alice", 90);
        
        // merge: 合并值
        // 参数:key, value, BiFunction(oldValue, newValue) -> mergedValue
        
        // key不存在,直接放入
        scores.merge("Bob", 85, (oldVal, newVal) -> oldVal + newVal);
        System.out.println(scores); // {Alice=90, Bob=85}
        
        // key存在,使用合并函数
        scores.merge("Alice", 10, (oldVal, newVal) -> oldVal + newVal);
        System.out.println(scores); // {Alice=100, Bob=85}
        
        // 返回null会删除key
        scores.merge("Bob", 0, (oldVal, newVal) -> null); // Bob被删除
        
        // 实际应用:统计词频
        String text = "apple banana apple cherry banana apple";
        Map<String, Integer> wordCount = new HashMap<>();
        for (String word : text.split(" ")) {
            wordCount.merge(word, 1, Integer::sum);
        }
        System.out.println(wordCount); // {banana=2, cherry=1, apple=3}
    }
}

6.6 replace / replaceAll

java 复制代码
public class MapReplaceDemo {
    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<>();
        scores.put("Alice", 90);
        scores.put("Bob", 85);
        
        // replace(key, value): 替换值(key必须存在)
        scores.replace("Alice", 95);
        System.out.println(scores); // {Alice=95, Bob=85}
        
        // replace(key, oldValue, newValue): 仅当旧值匹配时才替换
        boolean replaced = scores.replace("Bob", 80, 90); // false (Bob是85)
        replaced = scores.replace("Bob", 85, 90); // true
        
        // replaceAll: 使用BiFunction替换所有值
        scores.replaceAll((name, score) -> score + 5);
        System.out.println(scores); // {Alice=100, Bob=95}
    }
}

6.7 remove(key, value)

java 复制代码
public class MapRemoveDemo {
    public static void main(String[] args) {
        Map<String, Integer> scores = new HashMap<>();
        scores.put("Alice", 90);
        
        // remove(key): 删除key
        // remove(key, value): 仅当value匹配时才删除(原子操作)
        
        boolean removed = scores.remove("Alice", 85); // false (Alice是90)
        removed = scores.remove("Alice", 90); // true
        
        // 可用于并发场景的安全删除
    }
}

七、反射API的改进

JDK8对反射API进行了多项改进,特别是支持获取参数名和类型注解。

7.1 获取参数名

java 复制代码
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class ReflectionParameterNames {
    
    // 需要添加 -parameters 编译选项
    public void greet(String name, int age) {
        System.out.println("Hello " + name + ", you are " + age);
    }
    
    public static void main(String[] args) throws Exception {
        Method method = ReflectionParameterNames.class.getMethod("greet", String.class, int.class);
        
        // 获取参数信息
        Parameter[] params = method.getParameters();
        for (Parameter param : params) {
            System.out.println("参数名: " + param.getName());
            System.out.println("是否命名: " + param.isNamePresent());
            System.out.println("类型: " + param.getType());
            System.out.println("---");
        }
        
        // 注意:默认情况下参数名是arg0, arg1...
        // 需要使用 javac -parameters 编译才能获取真实参数名
    }
}

7.2 类型注解(Type Annotations)

JDK8引入了可以在任何使用类型的地方添加注解的能力。

java 复制代码
import java.lang.annotation.*;

// 定义可在类型上使用的注解
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface NonNull {
}

// 定义可为空的注解
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Nullable {
}
java 复制代码
import java.lang.reflect.*;
import java.util.List;

public class TypeAnnotationsDemo {
    
    // 类型注解的使用场景
    public void process(@NonNull String name, @Nullable String email) {
    }
    
    // 泛型类型注解
    public List<@NonNull String> getNames() {
        return null;
    }
    
    // 数组类型注解
    public void handleArray(@NonNull String @Nullable [] names) {
    }
    
    public static void main(String[] args) throws Exception {
        Method method = TypeAnnotationsDemo.class.getMethod("process", String.class, String.class);
        
        // 获取参数注解
        for (Parameter param : method.getParameters()) {
            System.out.println("参数: " + param.getName());
            
            // 获取类型注解
            AnnotatedType annotatedType = param.getAnnotatedType();
            for (Annotation ann : annotatedType.getAnnotations()) {
                System.out.println("  类型注解: " + ann.annotationType().getSimpleName());
            }
        }
        
        // 获取返回值的类型注解
        Method getNamesMethod = TypeAnnotationsDemo.class.getMethod("getNames");
        AnnotatedType returnType = getNamesMethod.getAnnotatedReturnType();
        System.out.println("返回类型: " + returnType.getType());
        
        // 获取泛型参数的类型注解
        if (returnType instanceof AnnotatedParameterizedType) {
            AnnotatedParameterizedType paramType = (AnnotatedParameterizedType) returnType;
            for (AnnotatedType typeArg : paramType.getAnnotatedActualTypeArguments()) {
                for (Annotation ann : typeArg.getAnnotations()) {
                    System.out.println("泛型参数注解: " + ann.annotationType().getSimpleName());
                }
            }
        }
    }
}

7.3 重复注解

java 复制代码
import java.lang.annotation.*;
import java.lang.reflect.*;

// 定义可重复的注解
@Repeatable(Schedules.class)
public @interface Schedule {
    String day();
    String time();
}

// 容器注解
public @interface Schedules {
    Schedule[] value();
}

// 使用重复注解
@Schedule(day = "Mon", time = "09:00")
@Schedule(day = "Wed", time = "14:00")
@Schedule(day = "Fri", time = "09:00")
public class Meeting {
}

// 反射获取重复注解
public class RepeatableAnnotationDemo {
    public static void main(String[] args) {
        Class<Meeting> clazz = Meeting.class;
        
        // 获取所有Schedule注解
        Schedule[] schedules = clazz.getAnnotationsByType(Schedule.class);
        for (Schedule s : schedules) {
            System.out.println(s.day() + " " + s.time());
        }
        
        // 获取容器注解
        Schedules container = clazz.getAnnotation(Schedules.class);
        if (container != null) {
            for (Schedule s : container.value()) {
                System.out.println("From container: " + s.day());
            }
        }
    }
}

八、Base64编码

JDK8内置了Base64编码支持,无需依赖第三方库(如Apache Commons Codec)。

8.1 基本使用

java 复制代码
import java.util.Base64;

public class Base64Demo {
    public static void main(String[] args) {
        String original = "Hello, World! 你好,世界!";
        
        // 获取编码器和解码器
        Base64.Encoder encoder = Base64.getEncoder();
        Base64.Decoder decoder = Base64.getDecoder();
        
        // 编码
        String encoded = encoder.encodeToString(original.getBytes());
        System.out.println("编码后: " + encoded);
        // 编码后: SGVsbG8sIFdvcmxkISDkvZzogIXjgIIx5LiW55WM77yB
        
        // 解码
        String decoded = new String(decoder.decode(encoded));
        System.out.println("解码后: " + decoded);
        // 解码后: Hello, World! 你好,世界!
    }
}

8.2 URL安全的Base64

java 复制代码
public class Base64UrlSafeDemo {
    public static void main(String[] args) {
        String data = "Hello+World/Test>Data?a=1&b=2";
        
        // 标准Base64包含 + 和 /,在URL中需要转义
        String standard = Base64.getEncoder().encodeToString(data.getBytes());
        System.out.println("标准: " + standard); // 包含 + /
        
        // URL安全的Base64使用 - 和 _ 代替
        String urlSafe = Base64.getUrlEncoder().encodeToString(data.getBytes());
        System.out.println("URL安全: " + urlSafe); // 使用 - _
        
        // 解码
        byte[] decoded = Base64.getUrlDecoder().decode(urlSafe);
        System.out.println("解码: " + new String(decoded));
    }
}

8.3 MIME格式的Base64

java 复制代码
public class Base64MimeDemo {
    public static void main(String[] args) {
        // 生成长文本
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 100; i++) {
            sb.append("Line ").append(i).append(" with some data. ");
        }
        String longText = sb.toString();
        
        // MIME格式:每76个字符换行,以\r\n结尾
        String mimeEncoded = Base64.getMimeEncoder().encodeToString(longText.getBytes());
        System.out.println(mimeEncoded);
        // 输出包含换行符,适合邮件传输
        
        // 自定义行长度
        Base64.Encoder customEncoder = Base64.getMimeEncoder(64, new byte[]{'\n'});
        String customEncoded = customEncoder.encodeToString(longText.getBytes());
    }
}

8.4 流式处理

java 复制代码
import java.io.*;

public class Base64StreamDemo {
    public static void main(String[] args) throws IOException {
        // 包装流进行Base64编码/解码
        
        // 编码到文件
        try (OutputStream os = Base64.getEncoder().wrap(
                new FileOutputStream("encoded.txt"))) {
            os.write("Hello, World!".getBytes());
        }
        
        // 从文件解码
        try (InputStream is = Base64.getDecoder().wrap(
                new FileInputStream("encoded.txt"))) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = is.read(buffer)) != -1) {
                System.out.write(buffer, 0, len);
            }
        }
    }
}

8.5 实际应用场景

java 复制代码
public class Base64RealWorldExamples {
    
    // 1. 图片Base64编码(用于网页内嵌)
    public static String imageToBase64(String imagePath) throws IOException {
        byte[] imageBytes = Files.readAllBytes(Paths.get(imagePath));
        return Base64.getEncoder().encodeToString(imageBytes);
    }
    // 使用: <img src="data:image/png;base64,iVBORw0KGgo...">
    
    // 2. JWT令牌(Header.Payload.Signature)
    public static String createJwtPart(String json) {
        return Base64.getUrlEncoder()
            .withoutPadding() // JWT不使用填充
            .encodeToString(json.getBytes());
    }
    
    // 3. Basic认证
    public static String basicAuth(String username, String password) {
        String credentials = username + ":" + password;
        return "Basic " + Base64.getEncoder().encodeToString(credentials.getBytes());
    }
    
    // 4. 数据传输编码
    public static String encodeForTransfer(byte[] data) {
        return Base64.getEncoder().encodeToString(data);
    }
}

九、踩坑提醒

9.1 Objects.requireNonNull的异常信息

java 复制代码
// 坑点1:消息在编译时确定,不会延迟计算
public void setName(String name) {
    // 即使name不为null,字符串拼接也会执行
    this.name = Objects.requireNonNull(name, "name cannot be null, time: " + System.currentTimeMillis());
}

// 正确做法:使用Supplier延迟计算
public void setNameCorrect(String name) {
    this.name = Objects.requireNonNull(name, () -> "name cannot be null, time: " + System.currentTimeMillis());
}

9.2 Map.compute的并发问题

java 复制代码
// 坑点2:compute方法不是原子操作
Map<String, Integer> map = new HashMap<>();

// 并发环境下可能出现问题
map.compute("key", (k, v) -> {
    // 这里的操作不是原子的
    // 如果有其他线程同时修改,可能导致不一致
    return (v == null) ? 1 : v + 1;
});

// 正确做法:使用ConcurrentHashMap
ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();
concurrentMap.compute("key", (k, v) -> (v == null) ? 1 : v + 1); // 线程安全

// 或者使用merge方法(更简洁)
concurrentMap.merge("key", 1, Integer::sum);

9.3 Map.compute返回null会删除key

java 复制代码
Map<String, Integer> map = new HashMap<>();
map.put("key", 100);

// 返回null会导致key被删除
map.compute("key", (k, v) -> null);
System.out.println(map.containsKey("key")); // false

// 如果只是想更新值,确保不返回null
map.compute("key", (k, v) -> {
    if (v == null) return 0;
    if (v > 100) return null; // 小心!这会删除key
    return v + 1;
});

9.4 Arrays.parallelSort的适用场景

java 复制代码
// 坑点4:小数组使用parallelSort反而更慢
int[] smallArray = {5, 2, 8, 1, 9};
Arrays.parallelSort(smallArray); // 不推荐,单线程更快

// 建议:数组长度大于10000时才考虑使用parallelSort
int[] largeArray = new int[100000];
// ... 填充数据
Arrays.parallelSort(largeArray); // 推荐

9.5 Base64编码后的数据膨胀

java 复制代码
// 坑点5:Base64编码会使数据膨胀约33%
byte[] original = new byte[1000];
String encoded = Base64.getEncoder().encodeToString(original);
System.out.println(encoded.length()); // 约1336字符

// 在存储或传输时要考虑这个因素

十、面试高频考点

Q1: Objects.equals和==的区别?

答案:

特性 == Objects.equals()
比较内容 比较引用(基本类型比较值) 比较对象内容
null处理 直接比较,不会NPE 安全处理null
适用场景 基本类型、引用比较 对象内容比较
java 复制代码
// == 比较引用
String a = new String("hello");
String b = new String("hello");
System.out.println(a == b); // false

// Objects.equals比较内容
System.out.println(Objects.equals(a, b)); // true

// null安全
System.out.println(Objects.equals(null, "hello")); // false
System.out.println(Objects.equals(null, null));    // true

Q2: Map.merge的作用?

答案:

merge(key, value, remappingFunction)用于合并Map中的值:

  • 如果key不存在,直接放入value
  • 如果key存在,使用remappingFunction合并旧值和新值

典型应用是词频统计:

java 复制代码
Map<String, Integer> wordCount = new HashMap<>();
for (String word : words) {
    wordCount.merge(word, 1, Integer::sum);
}

Q3: computeIfAbsent和putIfAbsent的区别?

答案:

特性 putIfAbsent computeIfAbsent
参数 直接传入value 传入value的生成函数
延迟计算 否(value立即创建) 是(只在需要时创建)
性能 每次都创建value对象 按需创建,更高效
适用场景 value创建成本低 value创建成本高
java 复制代码
// putIfAbsent: 每次都创建ArrayList
map.putIfAbsent(key, new ArrayList<>()); // 即使key存在也会创建

// computeIfAbsent: 按需创建
map.computeIfAbsent(key, k -> new ArrayList<>()); // key存在时不创建

Q4: JDK8反射获取参数名需要什么条件?

答案:

  1. 编译时必须使用-parameters选项:javac -parameters
  2. 使用Maven时配置:
xml 复制代码
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <compilerArgs>
            <arg>-parameters</arg>
        </compilerArgs>
    </configuration>
</plugin>
  1. 使用Parameter.isNamePresent()检查参数名是否可用

Q5: Base64.getEncoder()和getUrlEncoder()的区别?

答案:

  • getEncoder():标准Base64,使用+/,可能有=填充
  • getUrlEncoder():URL安全Base64,使用-_,无需URL编码

适用场景:

  • 标准编码:一般数据传输、文件编码
  • URL编码:JWT、URL参数、文件名

十一、总结

今天我们学习了JDK8中众多实用的工具类和API改进:

  1. Objects类:提供了null安全的equals、hashCode、toString等方法,简化日常开发
  2. String新方法:join、chars、codePoints、lines等,增强字符串处理能力
  3. Arrays新方法:parallelSort、setAll、parallelPrefix,支持并行数组操作
  4. Collections:sort、replaceAll等传统方法的补充
  5. Map新方法:forEach、compute、merge等函数式操作方法,极大提升代码简洁性
  6. 反射改进:参数名获取、类型注解、重复注解
  7. Base64编码:内置支持,无需第三方库

这些工具类虽然不如Lambda和Stream那样引人注目,但在实际开发中使用频率极高,掌握它们能让你的代码更加简洁、健壮。


十二、下一步预告

Day12 - JDK8实战与面试高频考点汇总

在系列的最后一篇,我们将:

  • 全景回顾JDK8所有新特性
  • 提供Lambda+Stream、Optional、日期时间、CompletableFuture的实战案例
  • 汇总各模块的面试高频考点(带详细答案)
  • 分享JDK8升级的注意事项和学习路线

敬请期待系列的收官之作!


十三、参考资料

  1. Java 8 API Documentation - Oracle
  2. Java 8新特性详解 - 官方教程
  3. Effective Java 3rd Edition - Joshua Bloch

十四、互动话题

  1. 你在项目中使用过哪些JDK8的工具类?遇到过什么坑?
  2. Map的compute和merge方法,你觉得哪个更实用?
  3. 对于反射获取参数名,你在实际项目中用过吗?

欢迎在评论区留言讨论!如果这篇文章对你有帮助,请点赞收藏支持一下~


相关推荐
索西引擎14 小时前
【LangChain 1.0】接入 DeepSeek API:从 API Key 申请到流式响应的完整实践
android·java·langchain
牧瀬クリスだ14 小时前
多线程安全:从原子性到锁机制
java·开发语言
山峰哥14 小时前
索引策略与SQL优化:从Explain对比到生产调优的完整方法论
android·java·数据库·sql·性能优化·深度优先
froginwe1114 小时前
Python File 方法
开发语言
float_com14 小时前
【java进阶】------反射与动态代理
java
woniu_buhui_fei14 小时前
分布式限流
java·分布式
Hanniel14 小时前
Python 元类(中):拦截类的创建
开发语言·python
我能坚持多久14 小时前
STL详解——priority_queue的使用以及模拟实现
开发语言·c++·priority_queue
D4c-lovetrain14 小时前
Jenkins 实战:Java 项目全自动打包、镜像构建、K8s 集群部署(完整CI/CD方案)
java·kubernetes·jenkins