JDK新特性
常用流操作
常见流操作概述:
| 使用流 | 流支持的方法 |
|---|---|
| 筛选 | filter、distinct |
| 切片 | takeWhile、dropWhile、limit、skip |
| 映射 | map、flatMap |
| 查找匹配 | allMatch、anyMatch、noneMatch、findFirst、findAny |
| 归约 | reduce |
| 使用数值流 | IntStream、DoubleStream 和 LongStream,支持对数值进行求和等操作,且没有装箱成本 |
| 无限流 | Stream.iterate 和 Stream.generate,用给定的函数按需创建值 |
下面列举了一些比较常用的流操作
流的扁平化
单词列表["Hello", "World"],返回列表["H", "e", "l", "o", "W", "r", "d"]
List<String> words = Arrays.asList("Hello", "World"); List<String> list = words.stream().map(word -> word.split("")) // 这里若使用map,得到的是 Stream<Stream<List>>; // flatMap可以使流扁平化,将生成的流合并,即最后得到的是一个 Stream<List> .flatMap(Arrays::stream) // Arrays.stream()方法可以接受一个数组并产生一个流 .distinct() .toList();常用流操作
List<User> users = Arrays.asList( new User("张三", 18), new User("李四", 20), new User("王李四", 29), new User("李五", 29), new User("王五", 17)); // 获取名字中所有不同的字;结果为: [张, 三, 李, 四, 王, 五] users.stream().map(user -> user.getName().split("")) .flatMap(Arrays::stream) .distinct() .toList(); // 计算总年龄 Integer ageAll = users.stream().mapToInt(User::getAge).sum(); Integer ageAll = users.stream().map(User::getAge) .reduce(0, (age1, age2) -> age1 + age2); // 给一个初始值,会被作为参数参与一次计算 Optional<Integer> ageAllOpt = users.stream().map(User::getAge) .reduce((age1, age2) -> age1 + age2); // 最大年龄 users.stream().max(Comparator.comparingInt(User::getAge)).get(); // 年龄汇总,返回一个汇总统计的对象,包含平均值、最大值、元素数量、最小值、总和 users.stream().collect(Collectors.summarizingInt(User::getAge)); // 姓名清单,结果为:张三-李四-王李四-王五 users.stream().map(User::getName).collect(Collectors.joining("-")); // 按年龄分组,且分组中只保留姓名;结果为:{17=[王五], 18=[张三], 20=[李四], 29=[王李四, 李五]} users.stream().collect(Collectors.groupingBy( User::getAge, Collectors.mapping(User::getName, Collectors.toList()) ));
集合
集合工厂
Collection API 引入了一些新的方法,可以很简便地创建由少量对象构成的 Collection。
// Arrays工厂创建一个不可改变长度的 List
List<String> list = Arrays.asList("one", "two", "three");
// List工厂创建一个不可改变长度的 List
List<String> list = List.of("one", "two", "three");
// Set工厂创建一个不可改变长度的 Set,若包含重复元素,会抛出异常
Set<String> set = Set.of("one", "two", "three");
// Map工厂初始化一个不可变Map,提供两种方式
Map<String, String> map = Map.of("1", "one", "2", "two"); // 不到10个键值
Map<String, String> map = Map.ofEntries(
Map.entry("1", "one"),
Map.entry("2", "two")); // 键值对规模比较大提示
集合工厂创建的都是不可变长的集合,若尝试添加或减少元素,会抛出 UnsupportedOperationException 异常
若想要创建的集合可被修改,只能通过 new,而非工厂方法;
为了避免不可预知的缺陷,不要在工厂方法创建的列表中存放 null 元素。
新增的集合方法
Java 8 在 List 和 Set 的接口中新引入了以下方法:
List<String> list = new ArrayList<>(Arrays.asList("one", "two", "three"));
// 1. removeIf方法
list.removeIf(item -> item.equals("two"));
// 2. replaceAll方法
list.replaceAll(String::toUpperCase);Java 8 在 Map 接口中新引入了几个默认方法:
Map<String, String> map = new HashMap<>();
map.put("1", "one");
map.put("2", "two");
// 1.遍历;
map.forEach((k, v) ->
System.out.println("Key: " + k + " Value: " + v)
);
// 2.排序;
map.entrySet().stream()
.sorted(Map.Entry.comparingByKey()) // 排序
.forEachOrdered(System.out::println); // 按顺序处理每个流
// 3.计算;
// 若需要根据key计算出value,且计算代价很大,下面列举的方法比较实用
// 以下方法若键符合条件,会执行计算并将结果添加到Map
map.computeIfAbsent("3", k -> "three" + k); // 键不存在或对应的值为空
map.computeIfPresent("3", (k, v) -> "three" + k); // 键存在
map.compute("3", (k, v) -> "three" + k); // 不管键是否存在
// 4.删除
map.remove("2", "two"); // 要key和value同时存在才会移除
// 5.替换
map.replaceAll((k, v) -> "牛逼" + v);
// 6.合并;结果为:{1=one, 2=new two && two}
// map的putAll方法也可以完成合并,但是有重复键则无法加入
// merge方法能更灵活的处理重复的键
Map<String, String> map1 = new HashMap<>();
map1.put("2", "new two");
HashMap<String, String> mapAll = new HashMap<>(map1);
map.forEach((k, v) ->
mapAll.merge(k, v, (v1, v2) -> v1 + " && " + v2));
System.out.println(mapAll);Optional
// 1. 创建Optional对象
Optional<User> optUser = Optional.empty(); // 创建一个空的Optional
Optional<User> optUser1 = Optional.of(user); // 接受一个非空值创建,若user为空则抛异常
Optional<User> optUser2 = Optional.ofNullable(user); // 允许接收null值创建
// 2. 读取Optional对象,更多方式查看下表
String userName = optUser.map(User::getName).get();
String carLogoName = optUser.flatMap(User::getCar)
.flatMap(Car::getLogo)
.map(Logo::getName)
.orElse("奔驰"); // 链式调用的任意一个环节返回一个空的Optional,那么结果就为空
optUser.flatMap(u -> car.map(c -> findLogo(u, c))); // 一个牛逼的对象属性获取示例
optUser.filter(u -> u.name.equals("张三")) // 值存在且满足过滤条件则返回,否则返回空Optional对象
.ifPresent(u -> System.out.println(u.getName()));提示
Stream 对象一样,Optional 也提供了类似的基础类型——OptionalInt、OptionalLong 以及 OptionalDouble;
不推荐使用基础类型的 Optional,因为基础类型的 Optional 不支持 map、flatMap 以及 filter 这些最有用的方法。
- 读取 Optional 对象的方法
方法 描述 get() 最简单但又最不安全的方法。若不存在则抛 NoSuchElementException 异常 orElse(T other) Optional 对象不包含值时提供一个默认值。 orElseGet(Supplier other) 是 orElse 方法的延迟调用版,Supplier 方法只有在 Optional 对象不含值时才执行 or(Supplier supplier) 如果 Optional 对象含有值,Supplier 不会执行任何操作,若不含值,会延迟地执行 Supplier orElseThrow(Supplier Supplier) 和 get 方法非常类似,只是 Optional 对象为空时可自定义抛出异常类型 ifPresent(Consumer consumer) 变量值存在时,以变量为 入参执行 Consumer,否则就不进行任何操作。 ifPresentOrElse(Consumer action, Runnable emptyAction) 和 ifPresent 类似,只是当变量不存在时,就执行 Runnable 所定义的动作。
新的日期和时间 API
时间工具概览
| 工具类 | 描述 |
|---|---|
| LocalDate | 实例不可变,提供简单的日期 |
| LocalTime | 一天中的时间 |
| LocalDateTime | 是 LocalDate 和 LocalTime 的合体 |
| Instant | 机器日期时间,可获用于取时间戳 Instant.now().getEpochSecond() Instant.now().atZone(ZoneId.of("Asia/Shanghai")) |
| Duration | 计算两个时间之间的间隔,相隔几秒、毫秒、分钟等 Duration.between(time1, time2); |
| Period | 计算两个日期之间的间隔,相隔几天、几月等 Period.between(date1, date2); |
时间的通用操作
LocalDate、LocalTime、LocalDateTime 以及 Instant 等日期-时间类提供了大量通用的方法,他们都是 Temporal 接口的实现,如下图所示:

LocalDate now = LocalDate.of(2025,10,1);
now.get(ChronoField.DAY_OF_MONTH); // 5
now.plus(2, ChronoUnit.DAYS); // 不改变原有对象,返回一个新的被修改后的值
now.with(ChronoField.MONTH_OF_YEAR,2); // 不改变原有对象,返回一个新的被修改后的值
now.format(DateTimeFormatter.ISO_LOCAL_DATE); // 2024-05-09
now.format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); // 2024/05/09
LocalDateTime.now().with(LocalTime.of(21, 0)); // 获取今晚九点整的 LocalDateTime 对象
// LocalDateTime 和 Date 互转
LocalDateTime localDateTime = LocalDateTime.of(2025, 10, 1, 10, 0, 0); // 2025-10-01 10:00:00
Date date = Date.from(localDateTime.atZone(ZoneId.of("Asia/Shanghai")).toInstant());
LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.of("Asia/Shanghai")).toLocalDateTime();
// 时间戳 和 LocalDateTime 互转
Instant now_1 = Instant.now();
// 新版时区java.time.ZoneId类是老版java.util.TimeZone类的替代品
now_1.atZone(ZoneId.of("Asia/ Shanghai")); // 绑定时区,ZoneId类中列举了所有的时区
now_1.getEpochSecond(); // 时间戳(秒)提示
一般而言,使用 of、parse、now、between 等创建的日期-时间对象都是不可修改的,这是为了更好地支持函数式编程,确保线程安全。
使用 get 和 with 方法,可以将 Temporal 对象值的读取和修改区分开。
时间的复杂操作
使用 TemporalAdjuster 可进行一些更加复杂的时间操作,比如,将日期调整到下个周日、下个工作日等。
LocalDate now = LocalDate.now();
now.with(TemporalAdjusters.lastDayOfMonth()); // 本月的最后一天
now.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY)); // 下歌周日
// 计算明天的日期,并过滤掉假日
now.with(n -> {
DayOfWeek week = DayOfWeek.of(n.get(ChronoField.DAY_OF_WEEK));
int dayAdd = 1;
if (week == DayOfWeek.FRIDAY) dayAdd = 3;
else if (week == DayOfWeek.SATURDAY) dayAdd = 2;
return n.plus(dayAdd, ChronoUnit.DAYS);
});TemporalAdjuster 类中的工厂方法

时间的复杂操作

