一、写在前面
欢迎来到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反射获取参数名需要什么条件?
答案:
- 编译时必须使用
-parameters选项:javac -parameters - 使用Maven时配置:
xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
- 使用
Parameter.isNamePresent()检查参数名是否可用
Q5: Base64.getEncoder()和getUrlEncoder()的区别?
答案:
getEncoder():标准Base64,使用+和/,可能有=填充getUrlEncoder():URL安全Base64,使用-和_,无需URL编码
适用场景:
- 标准编码:一般数据传输、文件编码
- URL编码:JWT、URL参数、文件名
十一、总结
今天我们学习了JDK8中众多实用的工具类和API改进:
- Objects类:提供了null安全的equals、hashCode、toString等方法,简化日常开发
- String新方法:join、chars、codePoints、lines等,增强字符串处理能力
- Arrays新方法:parallelSort、setAll、parallelPrefix,支持并行数组操作
- Collections:sort、replaceAll等传统方法的补充
- Map新方法:forEach、compute、merge等函数式操作方法,极大提升代码简洁性
- 反射改进:参数名获取、类型注解、重复注解
- Base64编码:内置支持,无需第三方库
这些工具类虽然不如Lambda和Stream那样引人注目,但在实际开发中使用频率极高,掌握它们能让你的代码更加简洁、健壮。
十二、下一步预告
Day12 - JDK8实战与面试高频考点汇总
在系列的最后一篇,我们将:
- 全景回顾JDK8所有新特性
- 提供Lambda+Stream、Optional、日期时间、CompletableFuture的实战案例
- 汇总各模块的面试高频考点(带详细答案)
- 分享JDK8升级的注意事项和学习路线
敬请期待系列的收官之作!
十三、参考资料
十四、互动话题
- 你在项目中使用过哪些JDK8的工具类?遇到过什么坑?
- Map的compute和merge方法,你觉得哪个更实用?
- 对于反射获取参数名,你在实际项目中用过吗?
欢迎在评论区留言讨论!如果这篇文章对你有帮助,请点赞收藏支持一下~