Java 8 的一些新特性
Java 8 的一些新特性
一、Lambda表达式
先看个集合排序的例子:
没用Lambda表达式前:
List<String> list = Arrays.asList("ccc", "ddd", "www", "aaa");
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
使用Lambda表达式:
List<String> list = Arrays.asList("ccc", "ddd", "www", "aaa");
Collections.sort(list, (o1, o2) -> {
return o1.compareTo(o2);
});
Lambda表达式让代码变得更精简,但是代码的可读性有所损失。
1、Lambda表达式格式
()
代表方法参数;{}
代表方法要实现的内容。
2、函数式接口
如果要用 Lambda,我们可以使用 @FunctionalInterface
注解去标记接口,并且该接口必须只有一个未实现的方法:
@FunctionalInterface
public interface LambdaInterface {
void hello(int i);
}
使用 lambda :
LambdaInterface lambda = (int i) -> {};
JDK8 中引入了 default 方法,允许我们通过
default
关键字对接口中定义的抽象方法提供一个默认的实现:
@FunctionalInterface public interface LambdaInterface { void hello(int i); default int add(int a, int b){ return a + b; } }
3、Lambda 表达式的四种写法
-
单个参数的
(int i) -> {}
或(i) -> {}
或i -> {}
-
多个参数
(int a, int b) -> {}
或(a, b) -> {}
-
写参数类型
(int i) -> {}
或(int a, int b) -> {}
-
方法体不止一行
如果方法体不止一行,需要用上
{}
,如果方法体只有一行,则可以不需要{}
。
4、JDK8 提供的函数式接口
接口 | 输入参数 | 返回类型 | 说明 |
---|---|---|---|
UnaryOperator | T | T | 一元函数,输入输出类型相同 |
Predicate | T | boolean | 断言 |
Consumer | T | / | 消费一个数据,只有输入没有输出 |
Function<T,R> | T | R | 输入 T 返回 R,有输入也有输出 |
Supplier | / | T | 提供一个数据,没有输入只有输出 |
BiFunction<T,U,R> | (T,U) | R | 两个输入参数 |
BiPredicate<L, R> | (L,R) | boolean | 两个输入参数 |
BiConsumer<T, U> | (T,U) | void | 两个输入参数 |
BinaryOperator | (T,T) | T | 二元函数,输入输出类型相同 |
-
UnaryOperator
import java.util.function.UnaryOperator; public class User { private String username; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String say(UnaryOperator<String> sayHello) { return sayHello.apply(this.username); } public static void main(String[] args) { User user = new User(); user.setUsername("tom"); String say = user.say((username) -> "hello " + username); System.out.println("say = " + say); } }
-
Predicate:输入一个 T 类型的参数,输出一个 boolean 类型的值
例如集合流Stream中的filter 方法中传入的就是一个 Predicate 函数接口:
list.stream().filter(s -> s.startsWith("a"))
-
Consumer:只有输入没有输出
例如集合的遍历就可以使用 Consumer 函数接口 :
list.stream().forEach(s -> System.out.println(s));
-
Supplier:只有输出没有输入
5、方法引用
操作符::
-
引用静态方法
// Function<Integer, String> func = a -> String.valueOf(a); Function<Integer, String> func = String::valueOf; String s = func.apply(99); // Consumer<String> consumer = s -> System.out.println(s); Consumer<String> consumer = System.out::println; consumer.accept("sssss");
-
实例方法引用
// IntUnaryOperator func = i -> random.nextInt(i); Random random = new Random(); IntUnaryOperator func = random::nextInt; Integer r = func.applyAsInt(10);
-
构造方法引用
// Supplier<Cat> supplier = () -> new Cat(); Supplier<Cat> supplier = Cat::new; Cat cat = supplier.get();
-
数组构造方法引用
// IntFunction<int[]> func = (i) -> new int[i]; IntFunction<int[]> func = int[]::new; int[] arr = func.apply(10);
6、变量引用
-
局部变量:内部类中使用外部定义的变量,需要这个变量是一个 final 类型的,如果用了 Lambda 表达式,这个规则依然适用。
int m = 0; LambdaInterface lambdaInterface = (i, j) -> { System.out.println(m + 3); };
与内部类不同的是我们不必显示的声明局部变量为final,但是它实际上已经是 final 类型的了,如果强行修改,就会报错。
-
成员变量及静态变量:在 Lambda 表达式中对成员变量和静态变量拥有读写权限。
二、Stream 流
JDK8 中引入了一种新式的流式 API —— Stream,名字看起来很像 Java IO 中的各种流操作,但这其实是两个完全不同的东西。Java8 中的 Stream 不存储数据,它通过函数式编程模式来对集合进行链状流式操作。
1、Stream 流获取
数组:
IntStream stream = Arrays.stream(new int[]{11, 22, 33, 44, 55, 66});
集合:
List<String> list = new ArrayList<>();
Stream<String> s1 = list.stream();
Set<String> set = new HashSet<>();
Stream<String> s2 = set.stream();
数字 Stream:
IntStream s1 = IntStream.of(1, 2, 3);
DoubleStream s2 = DoubleStream.of(1, 2, 3);
LongStream s3 = LongStream.of(1L, 2L, 3L);
自己创建:
Random random = new Random();
Supplier<Integer> supplier = () -> random.nextInt(100);
Stream<Integer> stream = Stream.generate(supplier).limit(5);
调用 Stream.generate 方法可以自己创建一个流,自己创建的时候需要提供一个 Supplier,通过调用 Supplier 中的 get 方法自动获取到元素。
2、Stream 流操作
流操作的操作主要分为两个部分:
- 中间操作:返回的任然是Stream 流;
- 终止操作:会返回一个结果。
2.1 中间操作
-
map:Stream 中最常用的一个转换方法,能够帮助我们将集合中的每一个元素做功能处理
// 给所有的元素乘 2,再打印 IntStream.of(1, 2, 3).map(i -> 2 * i).forEach(System.out::println);
JDK 中也提供了一些现成的格式转换,如下图:
可以直接将元素转为 Double、Long、Obj 等类型:String[] arr = {"1", "2", "3"}; Stream<String> s1 = Arrays.stream(arr); s1.mapToLong(i -> Long.parseLong(i)).forEach(System.out::println);
-
flatMap:flatMap 可以把 Stream 中的每个元素都映射为一个 Stream,然后再把这多个 Stream 合并为一个 Stream。
Stream<Integer> s = Stream.of(new Integer[]{1, 2, 3}, new Integer[]{4, 5, 6}, new Integer[]{7, 8, 9}).flatMap(i -> Arrays.stream(i)); s.forEach(System.out::println);
-
filter:filter 操作会对一个 Stream 中的所有元素一一进行判断,不满足条件的就被过滤掉了,剩下的满足条件的元素就构成了一个新的 Stream。
// 找到数组中所有大于 3 的元素 IntStream.of(2, 3, 4, 5, 6, 7).filter(i -> i > 3).forEach(System.out::println);
-
peek:peek 的入参是 Consumer,没有返回值,因此当我们要对元素内部进行处理时,使用 peek 是比较合适的。
-
distinct:去重。
IntStream.of(2, 3, 4, 3, 7, 6, 2, 5, 6, 7).distinct().forEach(System.out::println);
-
sorted:排序。
IntStream.of(2, 3, 4, 3, 7, 6, 2, 5, 6, 7).distinct().sorted().forEach(System.out::println);
-
limit/skip:limit 和 skip 配合操作有点像数据库中的分页,skip 表示跳过 n 个元素,limit 表示取出 n 个元素。
// 跳过 A 和 B,最终打印出 C D E Arrays.asList('A', 'B', 'C', 'D', 'E', 'F').stream().skip(2).limit(3).forEach(System.out::println);
2.2 终止操作
终止操作可以分为两类:
- 短路操作:不用处理全部元素就可以返回结果。
- 非短路操作:必须处理所有元素才能得到最终结果。
非短路操作:
-
forEach/forEachOrdered:forEach 和 forEachOrdered 都是接收一个 Consumer 类型的参数,完成对参数的消费,不同的是,在并行流中,forEachOrdered 会保证执行顺序。
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9}; Arrays.stream(arr).parallel().forEach(System.out::println); Arrays.stream(arr).parallel().forEachOrdered(System.out::println);
-
collect/toArray:这两个都是收集器,可以将执行结果转为一个 List 集合或者一个数组。
List<Integer> list = Stream.of(1, 2, 3, 4).filter(p -> p > 2).collect(Collectors.toList()); System.out.println(list);
-
reduce:reduce 是 Stream 的一个聚合方法,它可以把一个 Stream 的所有元素按照聚合函数聚合成一个结果。reduce 方法传入的对象是BinaryOperator 接口,它定义了一个apply 方法,负责把上次累加的结果和本次的元素进行运算,并返回累加的结果。
Optional<Integer> optional = Stream.of(1, 2, 3, 4).reduce((i, j) -> i + j); System.out.println(optional.orElse(-1));
reduce 的参数是 BinaryOperator,这个接收两个参数,第一个参数是之前计算的结果,第二个参数是本次参与计算的元素,两者累加求和。
-
min/max/count:求最大值最小值,统计总个数。
Stream<Integer> s = Stream.of(1, 2, 3, 4); long count = s.count(); System.out.println("count = " + count); Stream<Integer> s = Stream.of(1, 2, 3, 4); Optional<Integer> min = s.min(Comparator.comparingInt(i -> i)); System.out.println("min.get() = " + min.get());
短路操作:
-
findFirst/findAny:这两个就是返回流中的第一个 / 任意一个元素,findAny 要在并行流中测试才有效果。
for (int i = 0; i < 10; i++) { Optional<Integer> first = Stream.of(1, 2, 3, 4).parallel().findFirst(); System.out.println("first.get() = " + first.get()); } System.out.println("============="); for (int i = 0; i < 10; i++) { Optional<Integer> first = Stream.of(1, 2, 3, 4).parallel().findAny(); System.out.println("first.get() = " + first.get()); }
-
allMatch/anyMatch/noneMatch:allMatch、anyMatch、noneMatch 用来判断所有元素、任意元素或者没有元素满足给定的条件。这三个方法的参数都是一个 Predicate 接口函数。
boolean b = Stream.of(1, 2, 3, 4).allMatch(i -> i > 5); System.out.println("b = " + b);
2.3 并行流
Stream 流通过调用 parallel()
方法就能转换成一个可以并行处理的流,这在元素数量非常大的情况下,可以大大加快处理的速度。
new Random().ints().limit(50).parallel().forEach(i->{
System.out.println(Thread.currentThread().getName() + "--->" + i);
});
2.4 统计
一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("列表中最大的数 : " + stats.getMax());
System.out.println("列表中最小的数 : " + stats.getMin());
System.out.println("所有数之和 : " + stats.getSum());
System.out.println("平均数 : " + stats.getAverage());
三、Optional类
JDK8的类中引入了Optional类,设计它的目的是为了防止空指针异常。可以将 Optional
看做是包装对象的容器。当你定义了一个方法,这个方法返回的对象可能是空,也有可能非空的时候,你就可以考虑用 Optional
来包装它,这也是在 Java 8 被推荐使用的做法。
1、Optional类中的方法
序号 | 方法 | 方法说明 |
---|---|---|
1 | private Optional() |
无参构造,构造一个空Optional |
2 | private Optional(T value) |
根据传入的非空value构建Optional |
3 | public static<T> Optional<T> empty() |
返回一个空的Optional,该实例的value为空 |
4 | public static <T> Optional<T> of(T value) |
根据传入的非空value构建Optional,与Optional(T value)方法作用相同 |
5 | public static <T> Optional<T> ofNullable(T value) |
与of(T value)方法不同的是,ofNullable(T value)允许你传入一个空的value,当传入的是空值时其创建一个空Optional,当传入的value非空时,与of()作用相同 |
6 | public T get() |
返回Optional的值,如果容器为空,则抛出NoSuchElementException异常 |
7 | public boolean isPresent() |
判断当家Optional是否已设置了值 |
8 | public void ifPresent(Consumer<? super T> consumer) |
判断当家Optional是否已设置了值,如果有值,则调用Consumer函数式接口进行处理 |
9 | public Optional<T> filter(Predicate<? super T> predicate) |
如果设置了值,且满足Predicate的判断条件,则返回该Optional,否则返回一个空的Optional |
10 | public<U> Optional<U> map(Function<? super T, ? extends U> mapper) |
如果Optional设置了value,则调用Function对值进行处理,并返回包含处理后值的Optional,否则返回空Optional |
11 | public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) |
与map()方法类型,不同的是它的mapper结果已经是一个Optional,不需要再对结果进行包装 |
12 | public T orElse(T other) |
如果Optional值不为空,则返回该值,否则返回other |
13 | public T orElseGet(Supplier<? extends T> other) |
如果Optional值不为空,则返回该值,否则根据other另外生成一个 |
14 | public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)throws X |
如果Optional值不为空,则返回该值,否则通过supplier抛出一个异常 |
2、举例
Optional< String > fullName = Optional.ofNullable( null );
System.out.println( "Full Name is set? " + fullName.isPresent() );
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) );
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
// 结果:
// Full Name is set? false
// Full Name: [none]
// Hey Stranger!
Optional< String > firstName = Optional.of( "Tom" );
System.out.println( "First Name is set? " + firstName.isPresent() );
System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) );
System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
// 结果:
// First Name is set? true
// First Name: Tom
// Hey Tom!
四、新的日期 API
1、Clock
提供对当前日期和时间的访问。
获取当前毫秒数:
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
2、ZoneId
时区类,提供时区的相关操作。
获取所有时区:
System.out.println(ZoneId.getAvailableZoneIds());
// [Asia/Aden, America/Cuiaba, Etc/GMT+9, Etc/GMT+8, Africa/Nairobi, America/Marigot, Asia/Aqtau, Pacific/Kwajalein, America/El_Salvador,...
3、LocalTime
时间类,提供时间的相关操作。
获取当前默认时区下的时间:
System.out.println(LocalTime.now()); // 13:40:03.343
两个不同时区之间的时间比较:
ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);
System.out.println(now1.isBefore(now2)); // false
long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);
System.out.println(hoursBetween); // -3
System.out.println(minutesBetween); // -239
4、LocalDate
日期类,提供对日期的相关操作。
LocalDate today = LocalDate.now();
System.out.println(today); // 2021-11-19
// 今天加一天
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
// 明天减两天
LocalDate yesterday = tomorrow.minusDays(2);
// 2014 年七月的第四天
LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek); // 星期五
5、LocalDateTime
日期-时间类,可以将其看成是 LocalDate
和 LocalTime
的结合体。操作上,也大致相同。
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek); // 星期三
Month month = sylvester.getMonth();
System.out.println(month); // 十二月
// 获取改时间是该天中的第几分钟
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay); // 1439
Instant instant = sylvester
.atZone(ZoneId.systemDefault())
.toInstant();
Date legacyDate = Date.from(instant);
System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014
6、DateTimeFormatter
时间日期解析类。
// 解析字符串时间
DateTimeFormatter germanFormatter = DateTimeFormatter
.ofLocalizedTime(FormatStyle.SHORT)
.withLocale(Locale.GERMAN);
LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);
System.out.println(leetTime); // 13:37
// 解析字符串日期
DateTimeFormatter germanFormatter = DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.GERMAN);
LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas); // 2014-12-24
// 解析字符串日期时间
DateTimeFormatter formatter = DateTimeFormatter
.ofPattern("MMM dd, yyyy - HH:mm");
LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string); // Nov 03, 2014 - 07:13
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?