jdk8 新特性方法详解
----------------------------------------------------------------------------------
JDK 8(Java 8)是 Java 历史上极具里程碑意义的版本,引入了多项革命性特性,极大提升了代码简洁性、可读性和开发效率。以下是 JDK 8 的核心新特性及方法详解,包含用法、示例和应用场景:
一、Lambda 表达式(闭包)
语法
(参数列表) -> { 代码块 }
// 简化规则:
// 1. 若参数只有一个,可省略括号:param -> { ... }
// 2. 若代码块只有一行,可省略大括号和return:(a,b) -> a + b
关键前提:函数式接口
@FunctionalInterface注解校验)。JDK 8 内置了大量函数式接口(如java.util.function包下的Predicate、Function等)。示例:替代匿名内部类
// 传统匿名内部类(Runnable是函数式接口)
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("传统方式");
}
}).start();
// Lambda表达式简化
new Thread(() -> System.out.println("Lambda方式")).start();
实际应用:集合排序
List<String> list = Arrays.asList("apple", "banana", "cherry");
// 传统排序(匿名内部类)
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
// Lambda简化
Collections.sort(list, (a, b) -> a.length() - b.length());
二、Stream API(集合处理)
核心流程
- 创建流:从集合、数组等数据源获取流(
stream()串行流 /parallelStream()并行流)。 - 中间操作:对元素进行处理(过滤、映射、排序等,返回新流,可链式调用)。
- 终端操作:生成最终结果(如收集为集合、计算总和等,触发实际处理)。
常用方法及示例
List<User> users = Arrays.asList(
new User("张三", 20, "男"),
new User("李四", 25, "女"),
new User("王五", 18, "男")
);
// 1. 过滤(年龄>20)+ 提取姓名 + 收集为List
List<String> names = users.stream()
.filter(u -> u.getAge() > 20) // 中间操作:过滤
.map(User::getName) // 中间操作:提取姓名(方法引用)
.collect(Collectors.toList()); // 终端操作:收集结果
// 2. 排序(按年龄升序)+ 统计数量
long count = users.stream()
.sorted(Comparator.comparingInt(User::getAge)) // 按年龄排序
.count(); // 统计元素数
// 3. 并行流(多线程处理):计算年龄总和
int totalAge = users.parallelStream()
.mapToInt(User::getAge)
.sum();
核心优势
- 替代繁琐的
for循环,代码更简洁; - 并行流自动利用多核 CPU,无需手动处理多线程;
- 中间操作 “惰性执行”,仅在终端操作时才实际处理,提升效率。
三、Optional 类
null” 的对象,通过链式方法安全处理空值,避免NullPointerException(NPE)。核心方法
| 方法 | 作用 | 示例 |
|---|---|---|
ofNullable(T) |
包装可能为null的值 |
Optional.ofNullable(user) |
map(Function) |
若值存在,对其转换(如获取属性) | opt.map(User::getAge) |
ifPresent(Consumer) |
若值存在,执行操作 | opt.ifPresent(u -> System.out.println(u)) |
orElse(T) |
若值不存在,返回默认值 | opt.orElse(new User()) |
orElseThrow(Supplier) |
若值不存在,抛出指定异常 | opt.orElseThrow(() -> new RuntimeException()) |
示例:安全获取嵌套属性
// 避免多层null判断:获取用户的地址城市
String city = Optional.ofNullable(user) // 包装user(可能为null)
.map(User::getAddress) // 若user非null,获取address
.map(Address::getCity) // 若address非null,获取city
.orElse("未知城市"); // 任意环节为null,返回默认值
四、接口的默认方法(Default Methods)与静态方法
1. 默认方法(default关键字)
public interface MyInterface {
// 抽象方法(必须实现)
void abstractMethod();
// 默认方法(可直接调用,实现类可重写)
default void defaultMethod() {
System.out.println("接口的默认方法");
}
}
// 实现类
public class MyImpl implements MyInterface {
@Override
public void abstractMethod() {
System.out.println("实现抽象方法");
}
// 可选:重写默认方法
@Override
public void defaultMethod() {
System.out.println("重写默认方法");
}
}
2. 静态方法(static关键字)
public interface MathUtils {
static int add(int a, int b) {
return a + b;
}
}
// 调用
int sum = MathUtils.add(1, 2); // 3
应用场景
- 接口升级(如
Collection接口新增stream()默认方法,所有集合实现类无需修改即可使用); - 提供通用工具方法(如
Comparator.comparing()静态方法)。
五、方法引用(Method References)
四种引用方式
| 类型 | 语法 | 示例 |
|---|---|---|
| 静态方法引用 | 类名::静态方法 |
Integer::parseInt(替代s -> Integer.parseInt(s)) |
| 实例方法引用(对象) | 对象::实例方法 |
user::getName(替代u -> u.getName()) |
| 实例方法引用(类) | 类名::实例方法 |
String::length(替代s -> s.length()) |
| 构造方法引用 | 类名::new |
User::new(替代() -> new User()) |
示例:配合 Stream 使用
List<String> strList = Arrays.asList("1", "2", "3");
// 静态方法引用:将字符串转为整数
List<Integer> intList = strList.stream()
.map(Integer::parseInt) // 等价于 s -> Integer.parseInt(s)
.collect(Collectors.toList());
// 构造方法引用:创建User对象
List<User> userList = strList.stream()
.map(User::new) // 假设User有单参数构造方法:User(String name)
.collect(Collectors.toList());
六、新日期时间 API(java.time 包)
Date和Calendar,提供 immutable(不可变)、线程安全的日期时间处理类。核心类及用法
| 类名 | 作用 | 示例 |
|---|---|---|
LocalDate |
处理日期(年 / 月 / 日) | LocalDate.now() → 2023-10-01 |
LocalTime |
处理时间(时 / 分 / 秒) | LocalTime.of(14, 30) → 14:30 |
LocalDateTime |
处理日期 + 时间 | LocalDateTime.now() → 2023-10-01T14:30:00 |
Instant |
时间戳(UTC 时区) | Instant.now() → 2023-10-01T06:30:00Z |
Duration |
计算时间间隔 | Duration.between(time1, time2) |
Period |
计算日期间隔 | Period.between(date1, date2) |
DateTimeFormatter |
日期格式化(线程安全) | DateTimeFormatter.ofPattern("yyyy-MM-dd") |
示例:日期格式化与计算
// 1. 获取当前日期并格式化
LocalDate today = LocalDate.now();
String formatted = today.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
System.out.println(formatted); // 2023年10月01日
// 2. 计算3天后的日期
LocalDate future = today.plusDays(3); // 2023-10-04
// 3. 计算两个日期的间隔
LocalDate birthDate = LocalDate.of(1990, 1, 1);
Period period = Period.between(birthDate, today);
System.out.println("年龄:" + period.getYears() + "岁"); // 33岁(假设当前2023年)
七、CompletableFuture(异步编程)
Future,支持异步任务的链式操作、异常处理和结果组合,更灵活地处理多线程异步任务。核心优势
- 支持链式调用(
thenApply、thenAccept等); - 内置异常处理(
exceptionally); - 可组合多个异步任务(
thenCombine、allOf等)。
示例:异步任务链式执行
// 异步执行任务1:计算结果
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
System.out.println("任务1执行中...");
return 10; // 模拟耗时计算
});
// 任务1完成后,异步执行任务2(处理结果)
CompletableFuture<String> future2 = future1.thenApplyAsync(result -> {
System.out.println("任务2处理任务1的结果:" + result);
return "结果:" + (result * 2); // 任务1结果*2
});
// 获取最终结果(阻塞等待,实际开发中可配合回调)
String result = future2.get();
System.out.println(result); // 结果:20
异常处理
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
if (true) {
throw new RuntimeException("任务执行失败");
}
return 100;
})
.exceptionally(ex -> { // 捕获异常并返回默认值
System.out.println("捕获异常:" + ex.getMessage());
return 0; // 异常时返回0
});
System.out.println(future.get()); // 0
八、其他重要特性
-
重复注解(Repeatable Annotations):允许同一注解在同一位置重复使用(需定义容器注解)。java运行
@Repeatable(Roles.class) public @interface Role { String value(); } public @interface Roles { Role[] value(); } // 使用 @Role("admin") @Role("user") public class MyClass {} -
类型注解(Type Annotations):注解可用于类型声明(如泛型、参数类型),配合 Checker Framework 可进行编译时类型检查。java运行
List<@NonNull String> list; // 声明集合元素不能为null -
Nashorn JavaScript 引擎:支持在 Java 中运行 JavaScript 代码(Java 15 后已废弃,推荐使用 GraalVM)。
总结
- Lambda+Stream 简化了集合处理和函数式编程;
- Optional 解决了空指针痛点;
- 新日期 API 提供了安全易用的时间处理;
- CompletableFuture 提升了异步编程效率。
----------------------------------------------------------------------------------
一、Stream 核心概念
- 定义:Stream 是数据元素的序列,支持对元素进行连续、聚合的操作。它不存储数据,也不改变源数据,所有操作都返回新的流(或最终结果)。
- 特点:
- 惰性执行:中间操作不会立即执行,只有终端操作触发时才会一次性处理(优化性能)。
- 一次性:流只能被消费一次,处理完毕后自动关闭,再次使用会抛出异常。
- 并行支持:通过
parallelStream()可轻松实现多线程并行处理,无需手动管理线程。
二、Stream 操作流程
- 创建流:从数据源(集合、数组等)生成流。
- 中间操作:对元素进行处理(过滤、映射等),返回新的流(可链式调用)。
- 终端操作:生成最终结果(如集合、数值等),触发实际处理(流至此关闭)。
// 完整流程示例:从集合创建流 → 中间操作 → 终端操作
List<String> list = Arrays.asList("apple", "banana", "cherry", "date");
List<String> result = list.stream() // 1. 创建流(串行流)
.filter(s -> s.length() > 5) // 2. 中间操作:过滤长度>5的元素
.map(String::toUpperCase) // 2. 中间操作:转为大写
.sorted() // 2. 中间操作:排序
.collect(Collectors.toList()); // 3. 终端操作:收集为List
// 结果:[BANANA, CHERRY]
三、阶段 1:创建流(6 种常见方式)
| 数据源 | 创建方式 | 示例 |
|---|---|---|
| 集合 | stream()(串行)/ parallelStream()(并行) |
list.stream() / set.parallelStream() |
| 数组 | Arrays.stream(数组) |
Arrays.stream(new int[]{1,2,3}) |
| 单个值 | Stream.of(值1, 值2...) |
Stream.of("a", "b", "c") |
| 空流 | Stream.empty() |
Stream<String> emptyStream = Stream.empty(); |
| 无限流(迭代) | Stream.iterate(初始值, 迭代函数) |
Stream.iterate(0, n -> n+2).limit(5) → 0,2,4,6,8 |
| 无限流(生成) | Stream.generate(生成函数) |
Stream.generate(Math::random).limit(3) → 3 个随机数 |
四、阶段 2:中间操作(10 个核心方法)
1. 过滤(filter)
Predicate<T> 函数式接口)。// 过滤偶数
List<Integer> nums = Arrays.asList(1,2,3,4,5);
nums.stream()
.filter(n -> n % 2 == 0) // 保留偶数
.forEach(System.out::println); // 终端操作:打印 2,4
2. 映射(map / flatMap)
map:将元素转换为另一种类型(参数为Function<T, R>)。flatMap:将元素转换为流,再合并为一个流(解决 “流中流” 问题)。
// map:提取字符串长度
List<String> words = Arrays.asList("a", "bb", "ccc");
words.stream()
.map(String::length) // 每个字符串→长度
.forEach(System.out::println); // 1,2,3
// flatMap:将列表中的列表展平(如 [[1,2], [3,4]] → [1,2,3,4])
List<List<Integer>> listOfLists = Arrays.asList(
Arrays.asList(1,2),
Arrays.asList(3,4)
);
listOfLists.stream()
.flatMap(List::stream) // 每个子列表→流,再合并
.forEach(System.out::println); // 1,2,3,4
3. 排序(sorted)
- 自然排序(元素实现
Comparable):sorted() - 自定义排序(参数为
Comparator<T>):sorted(comparator)
// 自然排序(字符串按字典序)
List<String> strs = Arrays.asList("banana", "apple", "cherry");
strs.stream()
.sorted()
.forEach(System.out::println); // apple, banana, cherry
// 自定义排序(按长度倒序)
strs.stream()
.sorted((s1, s2) -> s2.length() - s1.length())
.forEach(System.out::println); // banana(6), cherry(6), apple(5)
4. 去重(distinct)
equals() 方法去重(类似 HashSet)。List<Integer> nums = Arrays.asList(1,2,2,3,3,3);
nums.stream()
.distinct()
.forEach(System.out::println); // 1,2,3
5. 限制(limit(n))
n 个元素(若流长度 < n,保留所有)。// 取前2个元素
Stream.iterate(1, n -> n+1) // 无限流:1,2,3,4...
.limit(2)
.forEach(System.out::println); // 1,2
6. 跳过(skip(n))
n 个元素(若流长度 < n,返回空流)。List<Integer> nums = Arrays.asList(1,2,3,4);
nums.stream()
.skip(2) // 跳过前2个
.forEach(System.out::println); // 3,4
7. 消费(peek)
List<String> strs = Arrays.asList("a", "b");
strs.stream()
.peek(s -> System.out.println("处理前:" + s)) // 调试
.map(String::toUpperCase)
.peek(s -> System.out.println("处理后:" + s))
.collect(Collectors.toList());
// 输出:
// 处理前:a
// 处理后:A
// 处理前:b
// 处理后:B
五、阶段 3:终端操作(12 个核心方法)
1. 收集(collect)
Collectors 工具类)。常用收集器(Collectors) | 作用 | 示例 |
|---|---|---|
toList() |
收集为 List |
stream.collect(Collectors.toList()) |
toSet() |
收集为 Set(去重) |
stream.collect(Collectors.toSet()) |
toMap(keyMapper, valueMapper) |
收集为 Map |
users.stream().collect(Collectors.toMap(User::getId, User::getName)) |
joining(分隔符) |
字符串拼接 | stream.collect(Collectors.joining(",")) → "a,b,c" |
groupingBy(分类函数) |
按条件分组(返回 Map<K, List<V>>) |
users.stream().collect(Collectors.groupingBy(User::getGender))(按性别分组) |
partitioningBy( predicate) |
按布尔条件分区(返回 Map<Boolean, List<V>>) |
nums.stream().collect(Collectors.partitioningBy(n -> n%2==0))(分为偶数 / 奇数两组) |
2. 计数(count)
long 类型)。long evenCount = Arrays.asList(1,2,3,4).stream()
.filter(n -> n%2 ==0)
.count(); // 2
3. 查找(findFirst / findAny)
findFirst():返回流中第一个元素(Optional<T>),适合有序流。findAny():返回流中任意一个元素(Optional<T>),适合并行流(效率更高)。
// 查找第一个偶数
Optional<Integer> firstEven = Arrays.asList(1,2,3,4).stream()
.filter(n -> n%2 ==0)
.findFirst(); // Optional[2]
// 并行流中查找任意偶数(可能是2或4)
Optional<Integer> anyEven = Arrays.asList(1,2,3,4).parallelStream()
.filter(n -> n%2 ==0)
.findAny();
4. 匹配(anyMatch / allMatch / noneMatch)
anyMatch(predicate):是否存在至少一个元素满足条件(boolean)。allMatch(predicate):是否所有元素都满足条件(boolean)。noneMatch(predicate):是否所有元素都不满足条件(boolean)。
List<Integer> nums = Arrays.asList(2,4,6);
boolean hasEven = nums.stream().anyMatch(n -> n%2 ==0); // true(存在偶数)
boolean allEven = nums.stream().allMatch(n -> n%2 ==0); // true(全是偶数)
boolean noOdd = nums.stream().noneMatch(n -> n%2 !=0); // true(没有奇数)
5. 归约(reduce)
- 无初始值:
reduce(BinaryOperator<T>)→ 返回Optional<T>(避免流为空时的问题)。 - 有初始值:
reduce(初始值, BinaryOperator<T>)→ 返回T(流为空时返回初始值)。
// 求和(有初始值0)
int sum = Arrays.asList(1,2,3).stream()
.reduce(0, Integer::sum); // 6(0+1+2+3)
// 求最大值(无初始值)
Optional<Integer> max = Arrays.asList(1,3,2).stream()
.reduce(Integer::max); // Optional[3]
6. 遍历(forEach)
Consumer<T>),无返回值。Arrays.asList("a", "b").stream()
.forEach(System.out::println); // 打印 a, b
7. 极值(min / max)
Optional<T>)。// 求字符串长度最小的元素
Optional<String> minStr = Arrays.asList("apple", "banana", "pear").stream()
.min(Comparator.comparingInt(String::length)); // Optional[pear](长度4)
六、并行流(parallelStream)
// 并行计算1-1000万的和(效率高于串行)
long sum = IntStream.rangeClosed(1, 10_000_000)
.parallel() // 转为并行流
.sum();
- 并行流基于
ForkJoinPool,默认线程数为 CPU 核心数。 - 线程安全问题:若中间操作涉及共享变量,需手动保证线程安全(如用
AtomicInteger)。 - 并非所有场景都快:小数据量场景下,并行流的线程开销可能超过收益。
七、Stream 与传统集合操作对比
传统方式(for循环) | Stream API |
|---|---|
| 命令式编程(需手动控制循环逻辑) | 声明式编程(只需描述 “做什么”) |
| 代码冗长,嵌套深(如多层过滤 / 转换) | 链式调用,代码简洁易读 |
| 并行处理需手动创建线程池,复杂 | 只需 parallelStream(),自动并行 |
| 立即执行(每步操作都处理数据) | 惰性执行(终端操作时一次性处理) |
八、核心总结
- 简洁性:用链式调用替代繁琐的
for循环和条件判断。 - 高效性:惰性执行 + 并行处理,优化大数据量场景的性能。
- 可读性:代码意图更清晰(如
filter明确是过滤,map明确是转换)。
----------------------------------------------------------------------------------
java.util.Optional<T> 是 JDK 8 引入的一个 “容器类”,专门用于封装可能为 null 的对象,其核心目标是优雅地避免空指针异常(NPE),替代传统的 null 检查逻辑,使代码更简洁、可读。一、为什么需要 Optional?
null 通常表示 “值不存在”,但直接操作可能为 null 的对象时,若未显式检查,极易触发 NullPointerException。例如:String str = null;
System.out.println(str.length()); // 直接抛出 NPE
if (obj != null) 判断,导致代码臃肿(被称为 “null 地狱”):// 传统 null 检查:获取用户的地址城市
String city = null;
if (user != null) {
Address address = user.getAddress();
if (address != null) {
city = address.getCity();
}
}
null 的值,并提供一系列方法安全处理值的存在性,彻底避免了显式 null 检查。二、Optional 核心概念
- 本质:Optional 是一个 “容器”,内部可能包含一个非
null的值,或为空(empty)。 - 设计原则:
- 不可变:创建后无法修改其包含的值(若有)。
- 无
null存储:Optional 自身永远不为null,其内部要么有值(非null),要么为空。 - 明确表达 “值可能不存在” 的语义:比直接返回
null更清晰地告知调用者 “需要处理空值情况”。
三、创建 Optional 实例(3 种方式)
| 方法 | 作用 | 适用场景 | 示例 |
|---|---|---|---|
Optional.of(T value) |
包装一个非 null 的值 |
明确知道值不可能为 null 时 |
Optional.of("hello")(若传 null 会抛 NPE) |
Optional.ofNullable(T value) |
包装一个可能为 null 的值 |
不确定值是否为 null 时(最常用) |
Optional.ofNullable(getUser()) |
Optional.empty() |
创建一个空 Optional(无值) | 明确表示 “值不存在” 时 | return Optional.empty(); |
四、核心方法详解(按使用场景分类)
1. 判断值是否存在
-
isPresent():返回boolean,表示 Optional 中是否有值(非空)。java运行Optional<String> opt = Optional.ofNullable("test"); if (opt.isPresent()) { System.out.println("值存在:" + opt.get()); } -
isEmpty()(JDK 11+):与isPresent()相反,判断值是否不存在。java运行if (opt.isEmpty()) { System.out.println("值不存在"); }
2. 若值存在则执行操作(避免 isPresent() + get())
-
ifPresent(Consumer<? super T> action):若值存在,则执行传入的消费操作(如打印、赋值);若不存在,不做任何事。java运行// 传统写法:if (str != null) { ... } // Optional 写法: Optional<String> opt = Optional.ofNullable(getName()); opt.ifPresent(name -> System.out.println("姓名:" + name)); // 仅当 name 非 null 时执行 -
ifPresentOrElse(Consumer, Runnable)(JDK 9+):若值存在执行消费操作,否则执行另一个操作。java运行opt.ifPresentOrElse( name -> System.out.println("姓名:" + name), () -> System.out.println("姓名不存在") );
3. 获取值(或处理 “无值” 情况)
-
get():直接获取值,但值不存在时会抛出NoSuchElementException(不推荐直接使用,相当于 “换一种方式抛异常”)。java运行try { String value = opt.get(); // 若 opt 为空,抛异常 } catch (NoSuchElementException e) { // 处理无值情况 } -
orElse(T other):若值存在则返回该值,否则返回默认值other(other无论是否用到都会被创建)。java运行// 若 user 为 null,返回默认用户 User user = Optional.ofNullable(getUser()).orElse(new User("默认用户")); -
orElseGet(Supplier<? extends T> supplier):若值存在则返回该值,否则通过supplier生成默认值(延迟创建,仅当需要时才执行)。java运行// 仅当 getUser() 返回 null 时,才调用 createDefaultUser() User user = Optional.ofNullable(getUser()).orElseGet(() -> createDefaultUser()); -
orElseThrow(Supplier<? extends X> exceptionSupplier):若值存在则返回该值,否则抛出supplier生成的异常(推荐用于 “值必须存在” 的场景)。java运行// 若用户不存在,抛出自定义异常 User user = Optional.ofNullable(getUser()) .orElseThrow(() -> new UserNotFoundException("用户不存在"));
4. 转换值(安全处理嵌套对象)
-
map(Function<? super T, ? extends U> mapper):若值存在,对其应用mapper转换,返回新的 Optional(包含转换后的值);若值不存在,返回空 Optional。java运行// 获取用户的年龄(若用户存在) Optional<User> userOpt = Optional.ofNullable(getUser()); Optional<Integer> ageOpt = userOpt.map(User::getAge); // 若 user 存在,返回 age 的 Optional -
flatMap(Function<? super T, Optional<U>> mapper):若值存在,对其应用mapper转换(mapper直接返回 Optional),避免嵌套 Optional(Optional<Optional<U>>)。java运行// 若用户存在,获取其地址的 Optional(假设 getUserAddress() 返回 Optional<Address>) Optional<Address> addressOpt = userOpt.flatMap(User::getUserAddress);
5. 过滤值
filter(Predicate<? super T> predicate):若值存在且满足predicate,返回当前 Optional;否则返回空 Optional。java运行// 过滤出年龄 > 18 的用户 Optional<User> adultUser = userOpt.filter(user -> user.getAge() > 18);
五、典型应用场景
场景 1:安全获取嵌套对象的属性(彻底消除多层 null 检查)
// 需求:获取用户的地址城市,若任意环节为 null,返回 "未知城市"
String city = Optional.ofNullable(user) // 包装可能为 null 的 user
.map(User::getAddress) // 若 user 非 null,获取 address(可能为 null)
.map(Address::getCity) // 若 address 非 null,获取 city(可能为 null)
.orElse("未知城市"); // 任意环节为 null,返回默认值
场景 2:方法返回值明确 “可能为空”(替代 null 返回)
// 传统方法:返回 null 表示“未找到”,调用者可能忘记检查
public User findUserById(Long id) {
return userRepository.findById(id); // 可能返回 null
}
// 优化:返回 Optional,明确告知调用者“可能无值”
public Optional<User> findUserById(Long id) {
return Optional.ofNullable(userRepository.findById(id));
}
// 调用者必须处理“无值”情况(更安全)
Optional<User> userOpt = userService.findUserById(1L);
User user = userOpt.orElseThrow(() -> new UserNotFoundException("用户不存在"));
场景 3:结合 Stream 处理集合中的空值
List<User> users = Arrays.asList(
new User("张三", 20),
null, // 集合中可能存在 null
new User("李四", 25)
);
// 过滤 null 并提取姓名
List<String> names = users.stream()
.map(Optional::ofNullable) // 将每个元素包装为 Optional
.filter(Optional::isPresent) // 保留有值的 Optional
.map(Optional::get) // 提取值(此时已非 null)
.map(User::getName)
.collect(Collectors.toList()); // [张三, 李四]
六、注意事项(避免滥用)
-
不推荐作为类的字段或方法参数:Optional 设计用于方法返回值,作为字段或参数会增加代码复杂度(如序列化问题)。
-
避免过度嵌套:
Optional<Optional<User>>这种嵌套结构是错误的,应使用flatMap扁平化。 -
慎用
get()方法:get()在值不存在时抛异常,违背 Optional 设计初衷,优先用orElse、orElseThrow等方法。 -
不要替代所有
null检查:简单场景下(如单一层级的null判断),传统if (obj != null)可能更直观。
七、总结
null 值” 的优雅解决方案,其核心价值在于:- 明确语义:通过返回 Optional 清晰告知调用者 “值可能不存在”,强制处理空值场景。
- 消除嵌套
if:用map链式调用替代多层null检查,代码更简洁。 - 减少 NPE:从根源上避免因忘记
null检查导致的空指针异常。
----------------------------------------------------------------------------------
java.time 包下),彻底解决了旧 API(java.util.Date、java.util.Calendar)的线程不安全、设计混乱(如月份从 0 开始)、API 命名不直观等问题。新 API 基于不可变对象设计,线程安全,且职责划分清晰,是处理日期时间的最佳实践。一、新日期 API 的核心优势
- 不可变性:所有类(如
LocalDate、LocalTime)都是不可变的,修改操作会返回新对象,避免线程安全问题。 - 清晰的职责划分:不同类处理不同场景(如日期、时间、日期 + 时间、时间戳等),API 命名直观。
- 线程安全:无需额外同步,可在多线程环境下安全使用。
- 统一的格式化:
DateTimeFormatter替代线程不安全的SimpleDateFormat,支持多种格式化方式。
二、核心类详解(按场景分类)
1. 本地日期时间(无时区信息)
| 类名 | 作用 | 示例(当前值) |
|---|---|---|
LocalDate |
仅包含日期(年 / 月 / 日) | 2023-10-01 |
LocalTime |
仅包含时间(时 / 分 / 秒 / 纳秒) | 14:30:25.123456789 |
LocalDateTime |
包含日期 + 时间 | 2023-10-01T14:30:25.123456789 |
(1)LocalDate:处理日期
// 获取当前日期
LocalDate today = LocalDate.now(); // 2023-10-01
// 创建指定日期(年, 月, 日)
LocalDate birthday = LocalDate.of(1990, 1, 1); // 1990-01-01
LocalDate christmas = LocalDate.of(2023, Month.DECEMBER, 25); // 用枚举指定月份
LocalDate date = LocalDate.of(2023, 10, 1);
// 增减日期
LocalDate tomorrow = date.plusDays(1); // 2023-10-02(加1天)
LocalDate lastMonth = date.minusMonths(1); // 2023-09-01(减1个月)
// 获取字段
int year = date.getYear(); // 2023
Month month = date.getMonth(); // OCTOBER(枚举)
int day = date.getDayOfMonth(); // 1
DayOfWeek dayOfWeek = date.getDayOfWeek(); // SUNDAY(周日)
// 比较
boolean isAfter = date.isAfter(LocalDate.of(2023, 9, 30)); // true(是否在之后)
boolean isLeapYear = date.isLeapYear(); // false(是否闰年)
(2)LocalTime:处理时间
// 获取当前时间
LocalTime now = LocalTime.now(); // 14:30:25.123456789
// 创建指定时间(时, 分, 秒, 纳秒)
LocalTime morning = LocalTime.of(8, 30); // 08:30:00
LocalTime preciseTime = LocalTime.of(14, 20, 30, 123456789); // 14:20:30.123456789
LocalTime time = LocalTime.of(14, 30);
// 增减时间
LocalTime later = time.plusHours(2); // 16:30(加2小时)
LocalTime earlier = time.minusMinutes(15); // 14:15(减15分钟)
// 获取字段
int hour = time.getHour(); // 14
int minute = time.getMinute(); // 30
(3)LocalDateTime:处理日期 + 时间
// 获取当前日期时间
LocalDateTime now = LocalDateTime.now(); // 2023-10-01T14:30:25
// 创建指定日期时间
LocalDateTime meeting = LocalDateTime.of(2023, 10, 1, 15, 0); // 2023-10-01T15:00
// 也可通过 LocalDate + LocalTime 组合
LocalDateTime combo = LocalDate.now().atTime(LocalTime.now());
LocalDateTime dt = LocalDateTime.of(2023, 10, 1, 14, 30);
// 转换为 LocalDate / LocalTime
LocalDate date = dt.toLocalDate(); // 2023-10-01
LocalTime time = dt.toLocalTime(); // 14:30
// 增减操作(支持年月日时分秒)
LocalDateTime nextWeek = dt.plusWeeks(1); // 2023-10-08T14:30
2. 时间戳(Instant)
Instant 表示UTC 时区的时间戳(从 1970-01-01T00:00:00Z 开始的毫秒数),类似旧 API 的 System.currentTimeMillis(),但精度更高(纳秒级)。// 获取当前时间戳(UTC)
Instant now = Instant.now(); // 2023-10-01T06:30:25.123456789Z(UTC时间,比北京时间晚8小时)
// 从毫秒数创建
Instant fromMillis = Instant.ofEpochMilli(System.currentTimeMillis());
// 增减时间(基于UTC)
Instant later = now.plus(1, ChronoUnit.HOURS); // 加1小时
// Instant → Date
Date date = Date.from(Instant.now());
// Date → Instant
Instant instant = new Date().toInstant();
3. 时间间隔(Duration)与日期间隔(Period)
Duration:计算两个时间(LocalTime/Instant)之间的间隔(秒、纳秒)。Period:计算两个日期(LocalDate)之间的间隔(年、月、日)。
(1)Duration:时间间隔
LocalTime start = LocalTime.of(10, 0);
LocalTime end = LocalTime.of(12, 30);
// 计算间隔
Duration duration = Duration.between(start, end);
// 获取间隔值
long hours = duration.toHours(); // 2(小时)
long minutes = duration.toMinutes(); // 150(分钟)
long seconds = duration.getSeconds(); // 9000(秒)
(2)Period:日期间隔
LocalDate startDate = LocalDate.of(2020, 1, 1);
LocalDate endDate = LocalDate.of(2023, 10, 1);
// 计算间隔
Period period = Period.between(startDate, endDate);
// 获取间隔值
int years = period.getYears(); // 3(年)
int months = period.getMonths(); // 9(月)
int days = period.getDays(); // 0(日)
4. 日期格式化与解析(DateTimeFormatter)
DateTimeFormatter 替代了旧 API 中线程不安全的 SimpleDateFormat,支持预定义格式和自定义格式,且线程安全。// 预定义格式(ISO标准)
DateTimeFormatter isoDate = DateTimeFormatter.ISO_LOCAL_DATE; // yyyy-MM-dd
DateTimeFormatter isoDateTime = DateTimeFormatter.ISO_LOCAL_DATE_TIME; // yyyy-MM-ddTHH:mm:ss
// 自定义格式(模式字母与 SimpleDateFormat 类似)
DateTimeFormatter customFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
LocalDateTime dt = LocalDateTime.of(2023, 10, 1, 14, 30);
String formatted = dt.format(customFormatter); // "2023年10月01日 14:30:00"
String str = "2023年10月01日 14:30:00";
LocalDateTime parsed = LocalDateTime.parse(str, customFormatter);
5. 时区处理(ZonedDateTime 与 ZoneId)
ZonedDateTime(带时区的日期时间)和 ZoneId(时区 ID)。// 获取所有可用时区ID(如 Asia/Shanghai, UTC)
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
// 上海时区的当前时间
ZonedDateTime shanghaiTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
// UTC时区的当前时间
ZonedDateTime utcTime = ZonedDateTime.now(ZoneId.of("UTC"));
// 时区转换(将上海时间转为纽约时间)
ZonedDateTime newYorkTime = shanghaiTime.withZoneSameInstant(ZoneId.of("America/New_York"));
三、与旧 API 的对比(为什么弃用旧 API?)
旧 API(Date/Calendar) | 新 API(java.time) |
|---|---|
| 可变对象(线程不安全) | 不可变对象(线程安全) |
类职责混乱(Date 既含日期又含时间) |
职责清晰(LocalDate/LocalTime 等) |
| 月份从 0 开始(1 月 = 0) | 月份从 1 开始(符合直觉) |
格式化依赖 SimpleDateFormat(线程不安全) |
用 DateTimeFormatter(线程安全) |
API 命名不直观(如 getYear() 返回 1900 年起的偏移量) |
命名直观(getYear() 直接返回年份) |
四、最佳实践
- 优先使用不可变类:
LocalDate、LocalTime等都是不可变的,避免修改原始对象,而是通过plusXxx()/minusXxx()生成新对象。 - 明确时区:若涉及跨时区,使用
ZonedDateTime而非LocalDateTime,避免时区混淆。 - 格式化用
DateTimeFormatter:彻底替代SimpleDateFormat,尤其在多线程环境。 - 避免旧 API:新项目直接使用
java.time包,旧项目逐步迁移(可通过toInstant()/from()转换)。
总结
LocalDate、LocalTime、LocalDateTime 覆盖了绝大多数本地场景,Instant 处理时间戳,Duration/Period 计算间隔,DateTimeFormatter 负责格式化,掌握这些类即可优雅地处理各类日期时间需求。----------------------------------------------------------------------------------
CompletableFuture 是 JDK 8 引入的异步编程工具类,基于 Future 接口扩展,解决了传统 Future 的局限性(如必须阻塞或轮询获取结果、无法链式处理多个异步任务、缺乏异常处理机制等)。它支持链式操作、异常处理、任务组合等高级特性,是 Java 异步编程的核心工具。一、核心优势
Future,CompletableFuture 的关键改进:- 非阻塞回调:无需阻塞等待结果,可通过回调函数处理异步结果。
- 链式操作:多个异步任务可按依赖关系串联,形成流水线。
- 任务组合:支持合并多个异步任务的结果(如 “等待所有任务完成” 或 “任一任务完成”)。
- 内置异常处理:提供专门的方法捕获和处理异步任务中的异常。
- 灵活性:可自定义线程池,避免默认线程池资源耗尽问题。
二、创建 CompletableFuture 实例
CompletableFuture 提供了多种创建方式,覆盖 “有返回值”“无返回值”“已完成任务” 等场景:| 方法 | 作用 | 适用场景 |
|---|---|---|
supplyAsync(Supplier<T>) |
异步执行有返回值的任务(用默认线程池) | 需要返回结果的异步操作 |
supplyAsync(Supplier<T>, Executor) |
异步执行有返回值的任务(用自定义线程池) | 需控制线程资源的场景 |
runAsync(Runnable) |
异步执行无返回值的任务(用默认线程池) | 无需返回结果的异步操作(如日志) |
runAsync(Runnable, Executor) |
异步执行无返回值的任务(用自定义线程池) | 同上,需控制线程资源 |
completedFuture(T value) |
创建已完成的任务(直接返回指定值) | 模拟异步结果或立即返回的场景 |
CompletableFuture<Void> empty() |
创建空的未完成任务 | 需手动完成的场景(配合 complete()) |
示例:创建基础异步任务
// 1. 有返回值的异步任务(默认线程池:ForkJoinPool.commonPool())
CompletableFuture<String> supplyTask = CompletableFuture.supplyAsync(() -> {
// 模拟耗时操作(如RPC调用、数据库查询)
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
return "任务结果";
});
// 2. 无返回值的异步任务(自定义线程池)
ExecutorService executor = Executors.newFixedThreadPool(3); // 自定义线程池(推荐)
CompletableFuture<Void> runTask = CompletableFuture.runAsync(() -> {
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("无返回值任务执行完毕");
}, executor);
// 3. 已完成的任务(立即获取结果)
CompletableFuture<String> completedTask = CompletableFuture.completedFuture("已完成的结果");
System.out.println(completedTask.get()); // 直接返回:已完成的结果
三、核心操作:链式处理异步结果
CompletableFuture 最强大的特性是链式操作:一个任务完成后,自动触发后续任务,形成 “流水线”。核心方法分为三类(按是否返回新结果划分):1. 处理结果并返回新值(thenApply 系列)
thenApply(Function<? super T,? extends U>):当前任务完成后,用Function处理结果,返回新的CompletableFuture<U>(同步执行后续任务,使用当前线程)。thenApplyAsync(Function...):后续任务异步执行(使用默认线程池或自定义线程池)。
// 示例:任务1返回数字,任务2将其转为字符串(链式处理)
CompletableFuture<Integer> task1 = CompletableFuture.supplyAsync(() -> 100);
// 同步处理:任务1完成后,当前线程执行转换
CompletableFuture<String> task2 = task1.thenApply(num -> "结果:" + num);
// 异步处理:任务1完成后,线程池执行转换(推荐,避免阻塞当前线程)
CompletableFuture<String> task3 = task1.thenApplyAsync(num -> "异步结果:" + num, executor);
// 获取最终结果(阻塞等待,实际开发中常用回调替代)
System.out.println(task2.get()); // 结果:100
2. 消费结果不返回新值(thenAccept 系列)
thenAccept(Consumer<? super T>):当前任务完成后,用Consumer消费结果(无返回值,CompletableFuture<Void>),同步执行。thenAcceptAsync(Consumer...):异步消费结果。
CompletableFuture<String> task = CompletableFuture.supplyAsync(() -> "Hello");
// 消费结果(打印)
task.thenAcceptAsync(result -> System.out.println("消费结果:" + result), executor);
// 输出:消费结果:Hello
3. 任务完成后执行动作(thenRun 系列)
thenRun(Runnable):当前任务完成后,执行Runnable(不关心前序结果),同步执行。thenRunAsync(Runnable...):异步执行。
CompletableFuture<Void> task = CompletableFuture.runAsync(() -> {
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
});
// 任务完成后执行后续动作
task.thenRunAsync(() -> System.out.println("前序任务已完成!"), executor);
// 输出:前序任务已完成!
四、异常处理
CompletableFuture 提供了专门的异常处理方法,避免异常被默默吞噬:1. exceptionally(Function<Throwable, ? extends T>)
catch)。CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {
// 模拟异常
if (true) throw new RuntimeException("任务执行失败");
return 100;
}).exceptionally(ex -> {
// 处理异常,返回默认值
System.out.println("捕获异常:" + ex.getMessage());
return 0; // 异常时返回0
});
System.out.println(task.get()); // 输出:0
2. handle(BiFunction<? super T, Throwable, ? extends U>)
try-finally)。CompletableFuture<String> task = CompletableFuture.supplyAsync(() -> {
if (true) throw new RuntimeException("失败");
return "成功结果";
}).handle((result, ex) -> {
if (ex != null) {
return "处理异常:" + ex.getMessage();
} else {
return "处理成功:" + result;
}
});
System.out.println(task.get()); // 输出:处理异常:失败
五、组合多个异步任务
CompletableFuture 支持将多个独立的异步任务按逻辑组合(如 “等待所有完成”“等待任一完成”“依赖多个结果”)。1. 等待两个任务完成后组合结果(thenCombine 系列)
thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V>):当前任务和other任务都完成后,用BiFunction组合结果。thenAcceptBoth(...):组合结果并消费(无返回值)。runAfterBoth(...):两个任务完成后执行动作(不关心结果)。
// 任务1:计算a=10
CompletableFuture<Integer> taskA = CompletableFuture.supplyAsync(() -> 10);
// 任务2:计算b=20
CompletableFuture<Integer> taskB = CompletableFuture.supplyAsync(() -> 20);
// 组合结果:a + b
CompletableFuture<Integer> sumTask = taskA.thenCombine(taskB, (a, b) -> a + b);
System.out.println(sumTask.get()); // 30
2. 等待任一任务完成(applyToEither 系列)
applyToEither(CompletionStage<? extends T> other, Function<? super T,? extends U>):当前任务或other任务中先完成的一个,用Function处理其结果。acceptEither(...):消费先完成的结果。runAfterEither(...):任一任务完成后执行动作。
// 任务1:2秒后返回"任务A"
CompletableFuture<String> taskA = CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
return "任务A";
});
// 任务2:1秒后返回"任务B"
CompletableFuture<String> taskB = CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
return "任务B";
});
// 取先完成的任务结果
CompletableFuture<String> firstTask = taskA.applyToEither(taskB, result -> "先完成的是:" + result);
System.out.println(firstTask.get()); // 1秒后输出:先完成的是:任务B
3. 等待多个任务全部完成(allOf)与任一完成(anyOf)
allOf(CompletableFuture<?>... cfs):等待所有任务完成(返回CompletableFuture<Void>,无返回值,需手动获取每个任务结果)。anyOf(CompletableFuture<?>... cfs):等待任一任务完成(返回CompletableFuture<Object>,结果为第一个完成的任务的结果)。
// 多个任务
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> "结果1");
CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> "结果2");
CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> "结果3");
// 1. 等待所有任务完成
CompletableFuture<Void> allTask = CompletableFuture.allOf(task1, task2, task3);
allTask.thenRun(() -> {
// 所有任务完成后,手动获取每个结果
try {
System.out.println(task1.get() + "," + task2.get() + "," + task3.get()); // 结果1,结果2,结果3
} catch (Exception e) { e.printStackTrace(); }
}).get();
// 2. 等待任一任务完成
CompletableFuture<Object> anyTask = CompletableFuture.anyOf(task1, task2, task3);
System.out.println(anyTask.get()); // 可能是结果1、结果2或结果3(取决于哪个先完成)
六、手动完成任务(complete 系列)
| 方法 | 作用 |
|---|---|
complete(T value) |
手动将任务标记为 “完成”,返回指定值 |
completeExceptionally(Throwable ex) |
手动将任务标记为 “异常完成”,抛出指定异常 |
CompletableFuture<String> manualTask = new CompletableFuture<>();
// 模拟外部事件触发完成(如异步回调)
new Thread(() -> {
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
manualTask.complete("手动完成的结果"); // 手动设置结果
}).start();
System.out.println(manualTask.get()); // 1秒后输出:手动完成的结果
七、线程池选择(重要!)
- 默认线程池:
supplyAsync/runAsync不指定线程池时,使用ForkJoinPool.commonPool(),其线程数默认等于 CPU 核心数(适合计算密集型任务)。 - 自定义线程池:IO 密集型任务(如网络请求、数据库操作)建议使用自定义线程池(如
Executors.newFixedThreadPool(n)),避免默认线程池被耗尽。
// 自定义线程池(IO密集型任务,线程数可设为CPU核心数*2)
ExecutorService ioExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
// 使用自定义线程池执行任务
CompletableFuture.supplyAsync(() -> {
// 执行IO操作(如HTTP请求)
return "IO结果";
}, ioExecutor);
八、与传统 Future 的对比
| 特性 | 传统 Future | CompletableFuture |
|---|---|---|
| 获取结果 | 需阻塞(get())或轮询 |
支持回调(非阻塞) |
| 链式操作 | 不支持(需手动嵌套) | 支持(thenApply 等方法) |
| 异常处理 | 需在任务内部捕获,外部难处理 | 提供 exceptionally/handle 等方法 |
| 任务组合 | 不支持 | 支持 thenCombine/allOf 等 |
| 灵活性 | 低(仅能获取结果) | 高(支持手动完成、线程池自定义) |
总结
CompletableFuture 是 Java 异步编程的 “瑞士军刀”,其核心价值在于:- 用链式操作简化异步任务的依赖关系,代码更清晰;
- 提供完善的异常处理,避免异步任务中的异常被忽略;
- 支持多任务组合,轻松实现 “等待所有”“等待任一” 等逻辑;
- 可自定义线程池,灵活控制资源,避免性能问题。
CompletableFuture 能显著提升异步代码的可读性和可维护性,是处理并发任务(如微服务调用、批量处理)的必备技能。----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------
----------------------------------------------------------------------------------

浙公网安备 33010602011771号