运用 JDK8 中的核心新特性
文章目录
- 运用 JDK8 中的核心新特性
- 1)引入了 Lambda 表达式
- 1.1 函数式接口
- 1.2 自定义函数式接口
- 1.3 使用自带的函数式接口
- 2)新增 Stream 流式接口
- 3)引入 Optional 类
- 4)引入了易用的日期类
- 4.1 更简单的日期计算API
- 4.2 获取指定日期的API
- 5)接口默认方法、静态方法
- 5.1 默认方法和静态方法
- 5.2 使用
- 6)新增 CompletableFuture 类
- 6.1 常见操作
1)引入了 Lambda 表达式
Lambda 表达式是一种匿名的函数,可以理解成它是一种可以传递的代码,像数据一让传递,让代码更简洁和灵活。
Lambda 表达式的语法:(入参) -> {方法体} 【()读小括号,{}读花括号】
举个例子????:
// 1.传统方式
Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.println("你好天天");
}
};
// 2.Lambda 表达式(只有单个表达式的,可以省略{})
Runnable runnable2 = () -> System.out.println("你好天天");
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
numbers.forEach(number -> System.out.print(number+" "));
// 3.Lambda表达式(使用多个语句块,不可用省略{})
numbers.forEach(number -> {
int doubled = number * 2;
System.out.println(number + " doubled: " + doubled);
});
1.1 函数式接口
在上面例子中,forEach
方法为什么就能参入一段 Lambda 表达式呢?一路点进去发现它是个函数式接口,接口上加上了@FunctionalInterface
注解。
什么是函数式接口:
1)一个接口只有一个抽象方法,那么该接口就是一个函数式接口
2)接口上声明了 @FunctionalInterface
注解,就一定是函数式接口,如果你接口中保证了一个接口只有一个抽象方法就可以不加这个注解,编译器也会认为你这个是函数式接口。
1.2 自定义函数式接口
背景:假设我是一位厨师,需要有一位助手来帮我。你给助手提供了一个简单的任务:切洋葱。你告诉助手只需要进行切菜的操作,其他的工作你会负责。
在这个例子中,我们可以将这个任务看作是一个接口,而助手则是接口的实现者。这个接口定义了一个方法,即切菜的操作。
@FunctionalInterface
public interface Task {
// 定义一个函数式接口,接受一个List<String>作为参数
void perform(List<String> dishes);
}
public static void main(String[] args) {
// 创建一个助手对象,使用Lambda表达式实现任务
// 这里我们传递一个包含多种菜品的列表
List<String> dishes = Arrays.asList("洋葱", "胡萝卜", "土豆");
// 使用 Lambda 表达式作为参数传递给方法
Task assistant = dishesList -> System.out.println("助手正在切以下菜品: " + dishesList);
// 调用厨师的方法,传递助手对象执行任务
cookMeal(assistant, dishes);
}
public static void cookMeal(Task task, List<String> dishes) {
// 准备食材,这里可以打印出需要准备的菜品列表
System.out.println("准备以下食材: " + dishes);
// 执行任务
task.perform(dishes);
// 煮菜
System.out.println("开始烹饪...");
}
执行结果如下:
1.3 使用自带的函数式接口
常用的几种 Function
Consumer
Predicate
Supplier
函数接口 | 描述 |
---|---|
Function<T, R> | 接收一个参数并返回结果,返回 R |
Consumer< T> | 对给定的参数执行操作,无返回值 |
Predicate< T> | 根据给定的参数进行判断,返回 boolean |
Supplier< T> | T get() 获取函数的结果值,返回 T |
Function 使用示例:
public static void main(String[] args) {
int[] nums = new int[]{1, 2, 3, 4, 5, 6};
// 原数组每个元素 * 2
elementDouble(nums, e -> e * 2);
System.out.println("e -> e * 2 = " + Arrays.toString(nums));
// 数组每个元素 + 1
elementDouble(nums, e -> e + 1);
System.out.println("e -> e + 1 = " + Arrays.toString(nums));
}
// Function<入参的泛型, 出参的泛型>
public static void elementDouble(int[] nums, Function<Integer, Integer> function) {
for (int i = 0; i < nums.length; i++) {
// 执行传入的函数 function 拿到返回值后重新赋值给 nums[i]
nums[i] = function.apply(nums[i]);
}
}
2)新增 Stream 流式接口
Stream
将要处理的元素集合看作一种流,在流的过程中,借助Stream API
对流中的元素进行操作,比如:筛选、排序、聚合等。
Stream可以由数组或集合创建,对流的操作分为两种:
- 中间操作,每次返回一个新的流,可以有多个。(筛选 filter、映射 map、排序 sorted、去重组合 skip—limit)
- 终端操作,每个流只能进行一次终端操作,终端操作结束后流无法再次使用。终端操作会产生一个新的集合或值。(遍历 foreach、匹配 find–match、规约 reduce、聚合 max–min–count、收集 collect)
常见用法:
List<String> list = Arrays.asList("a", "b", "c", "d");
// ====== filter ======
List<String> result = list.stream()
.filter(s -> s.startsWith("a")) // 中间操作
.collect(Collectors.toList()); // 终端操作
// ====== map ======
List<Integer> intListNew = intList.stream()
.map(x -> x + 3).collect(Collectors.toList());
// ====== reduce ======
Optional<Integer> reduce = list.stream().reduce(Integer::sum); // Integer::sum:求和 Integer:::max求最大值 Integer::min求最小值
// ====== sorted ======
List<String> newList = personList.stream()
.sorted(Comparator.comparing(Person::getSalary)) // 按照工资正序排序
.map(Person::getName)
.collect(Collectors.toList());
// ====== 归集(toList、toSet、toMap) ======
List<Integer> listNew = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toList()); // toList
Set<Integer> set = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toSet()); // toSet
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, 23, "male", "New York"));
Map<?, Person> map = personList.stream().filter(p -> p.getSalary() > 8000)
.collect(Collectors.toMap(Person::getName, p -> p)); // toMap
3)引入 Optional 类
Optional 类主要是避免空指针问题的一个判空工具类,Optional
可以包含一个值,也可以为空,从而表示“值存在”或“值不存在”这两种状态。
-
of(T value)
:创建一个包含非空值的Optional
。 -
empty()
:创建一个空的Optional
。 -
get()
:获取Optional
中的值(不推荐直接使用,可能抛出异常)。 -
isPresent()
:判断Optional
中是否包含值。 -
ifPresent(Consumer<? super T>)
:如果Optional
中包含值,则执行指定的操作。 -
orElse(T other)
:如果Optional
为空,返回默认值。 -
orElseGet(Supplier<? extends T>)
:如果Optional
为空,执行Supplier
提供的操作,返回值。
4)引入了易用的日期类
JDK8 提供了更简洁易用的日期类(java.time包下)如:
LocalDateTime.class //日期+时间 format: yyyy-MM-dd HH:mm:ss.SSS
LocalDate.class //日期 format: yyyy-MM-dd
LocalTime.class //时间 format: HH:mm:ss
LocalDate date = LocalDate.now();
LocalTime time = LocalTime.now();
LocalDateTime dateTime = LocalDateTime.now();
4.1 更简单的日期计算API
public void pushWeek(){
// 计算一周后的日期
LocalDate localDate = LocalDate.now();
// 方法1
LocalDate after = localDate.plus(1, ChronoUnit.WEEKS);
// 方法2
LocalDate after2 = localDate.plusWeeks(1);
System.out.println("一周后日期:" + after);
// 算两个日期间隔多少天,计算间隔多少年,多少月
LocalDate date1 = LocalDate.parse("2024-10-1");
LocalDate date2 = LocalDate.parse("2025-10-1");
Period period = Period.between(date1, date2);
System.out.println("date1 到 date2 相隔:"
+ period.getYears() + "年"
+ period.getMonths() + "月"
+ period.getDays() + "天");
// 打印结果是 “date1 到 date2 相隔:1年0月0天”
// 这里period.getDays()得到的天是抛去年月以外的天数,并不是总天数
// 如果要获取纯粹的总天数应该用下面的方法
long day = date2.toEpochDay() - date1.toEpochDay();
System.out.println(date1 + "和" + date2 + "相差" + day + "天");
// 打印结果:2024-10-01和2025-10-01相差365天
}
4.2 获取指定日期的API
public void getDayNew() {
LocalDate today = LocalDate.now();
//获取当前月第一天:
LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth());
// 取本月最后一天
LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth());
//取下一天:
LocalDate nextDay = lastDayOfThisMonth.plusDays(1);
//当年最后一天
LocalDate lastday = today.with(TemporalAdjusters.lastDayOfYear());
//2024年最后一个周日
LocalDate lastMondayOf2021 = LocalDate.parse("2024-12-31").with(TemporalAdjusters.lastInMonth(DayOfWeek.SUNDAY));
}
5)接口默认方法、静态方法
默认方法允许在接口中定义方法的默认实现,这样接口的实现类不需要再实现这些方法。之所以提供静态方法,是为了将相关的方法内聚在接口中,而不必创建新的对象。
5.1 默认方法和静态方法
interface MyInterface {
default void defaultMethod() {
System.out.println("Default Method");
}
static void hello() {
System.out.println("Hello, New Static Method Here");
}
}
5.2 使用
// 无需强制重写任何方法,因为默认方法和静态方法都有方法体,非抽象方法
public class ConcreteRealization implements MyInterface{
// 可以选择重写默认方法,不重写就调用接口实现的那个
// @Override
// public void defaultMethod() {
// System.out.println("mememmememe");
// }
}
public static void main(String[] args) {
MyInterface myInterface = new ConcreteRealization();
// 实现类调用默认方法
myInterface.defaultMethod();
// 打点直接调用
MyInterface.hello();
}
// 输出
Default Method
Hello, New Static Method Here
6)新增 CompletableFuture 类
CompletableFuture 提供了一个新的异步编程模型,简化了异步任务的编写和管理。
6.1 常见操作
主要介绍 runAsync()
supplyAsync()
thenApply()
handle()
thenCompose()
thenCombine
allOf
anyOf
方法
// ===== 静态方法 supplyAsync(有返回值) runAsync(无返回值)=====
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);
// 使用自定义线程池(推荐)
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor);
static CompletableFuture<Void> runAsync(Runnable runnable);
// 使用自定义线程池(推荐)
static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);
// ===== 处理异步计算后的结果 =====
// 沿用上一个任务的线程池
public <U> CompletableFuture<U> thenApply(
Function<? super T,? extends U> fn) {
return uniApplyStage(null, fn);
}
// ===== 异常处理 =====
public <U> CompletableFuture<U> handle(
BiFunction<? super T, Throwable, ? extends U> fn) {
return uniHandleStage(null, fn);
}
public <U> CompletableFuture<U> handleAsync(
BiFunction<? super T, Throwable, ? extends U> fn) {
return uniHandleStage(defaultExecutor(), fn);
}
public <U> CompletableFuture<U> handleAsync(
BiFunction<? super T, Throwable, ? extends U> fn, Executor executor) {
return uniHandleStage(screenExecutor(executor), fn);
}
// ===== 组合 CompletableFuture =====
// thenCompose 可以链接两个 CompletableFuture 对象,并将前一个任务的返回结果作为下一个任务的参数,它们之间存在着先后顺序。
public <U> CompletableFuture<U> thenCompose(
Function<? super T, ? extends CompletionStage<U>> fn) {
return uniComposeStage(null, fn);
}
// thenCombine 会在两个任务都执行完成后,把两个任务的结果合并。两个任务是并行执行的,它们之间并没有先后依赖顺序。
public <U,V> CompletableFuture<V> thenCombine(
CompletionStage<? extends U> other,
BiFunction<? super T,? super U,? extends V> fn) {
return biApplyStage(null, other, fn);
}
// ===== 并行运行多个 CompletableFuture =====
// allOf() 方法会等到所有的 CompletableFuture 都运行完成之后再返回
CompletableFuture.allOf(task1,.....,task6);
// allOf() 方法会等到所有的 CompletableFuture 都运行完成之后再返回
CompletableFuture.anyOf(task1,.....,task6);
BiFunction<? super T,? super U,?