Java 8 新特性深度解析:探索 Lambda 表达式、Stream API 和函数式编程的革新之路

Java8 新特性

Java 8 的革新之路

自 1995 年首次发布以来,Java 已经成为世界上最广泛使用的编程语言之一。随着时间的推移,Java 经历了多次版本更新,其中最具里程碑意义的便是 Java 8 的发布。这个版本引入了许多重大变革,包括 Lambda 表达式、Stream API 和函数式编程等新特性。这些变化不仅提升了 Java 开发者的生产力,还使得 Java 在性能、简洁性和可读性方面达到了新的高度。

本文将深入探讨 Java 8 的主要新特性,并通过示例代码和实际应用案例帮助你理解这些特性的优势和使用场景。无论你是 Java 初学者还是经验丰富的开发者,这篇文章都将为你提供一个全面了解 Java 8 新特性的平台,帮助你在日常开发中更高效地利用这些功能。

让我们一起踏上 Java 8 的革新之旅,探索如何通过这些新特性提高我们的代码质量和开发效率。

主要内容

  1. Lambda 表达式(核心)
  2. 函数式接口
  3. 方法引用与构造器引用
  4. Stream API
  5. 新时间日期 API
  6. 接口中默认方法与静态方法
  7. 其他新特性

新特性简介

  • 速度更快
  • 代码更少(简洁,增加了新的语法 Lambda 表达式
  • 强大的 Stream API
  • 便于并行
  • 可以最大化减少空指针异常 Optional – 因为这个类的方法

为什么便于运行?

三点原因:

  1. Stream API 可以让我们轻松地对集合进行并行处理。
  2. Lambda表达式 可以让我们以一种简洁的方式定义匿名函数,这种方式可以使代码更加简洁。
  3. 方式引用已经存在的方法或构造函数,可以避免我们重复编写相似的代码。

综上所述,Java8 新特性便于并行的原因是,它们可以让我们以一种简洁的方式处理集合数据,同时充分利用多核 CPU 的优势,从而提高程序的执行效率。

1、Lambda 表达式

何为 Lambda 表达式?

Lambda 是一个匿名函数,作为一种更紧凑的代码风格,使 Java 的语言表达能力得到了提升,可以写出更简洁、更灵活的代码。

代码示例

  1. Runnable 接口,从匿名内部类到 Lambda 的转换**(无参)**
// 匿名内部类
Runnable r1 = new Runnable() {@Overridepublic void run() {System.out.println("Hello World!");}
};// Lambda 表达式
Runnable r1 = () -> System.out.println("Hello Lambda!");
  1. Comparator 接口,参数传递 – 从匿名内部类到 Lambda 的转换**(有参)**
// 使用匿名内部类作为参数传递
TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {@Overridepublic int compare(String o1, String o2) {return Interger.compare(o1.length(), o2.length());}
});// 使用 Lambda 表达式作为参数传递
TreeSet<String> ts2 = new TreeSet<>((o1, o2) -> Integer.compare(o1.length(), o2.length()) // 升序排序
);
  1. Listener 接口
JButton button = new JButton();
button.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {e.getItem();
}
});// Lambda
button.addItemListener(e -> e.getItem());

语法

-> 箭头操作符简介

Lambda 表达式在 Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “->”,该操作符被称为 Lambda 操作符或箭头操作符

它将 Lambda 分为两个部分:

  • 左侧:指定了 Lambda 表达式需要的所有参数
  • 右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能
六种格式
格式一:

无参,无返回值,Lambda 体只需要一条语句即可

Runnable r1 = () -> System.out.println("Hello Lambda!");
格式二:

Lambda 需要一个参数

Consumer<String> fun = (args) -> System.out.println(args);
格式三:

Lambda 只需要一个参数时,参数的小括号可以省略

Consumer<String> fun = args -> System.out.println(args);
格式四:

Lambda 需要两个参数,并且有返回值时

BinaryOperator<Long> bo = (x, y) -> {System.out.println("实现函数接口方法!");return x + y;
};
格式五:

当 Lambda 体只有一条语句时,return 与大括号可以省略

BinaryOperator<Long> bo = (x, y) -> x + y;
格式六:

带参数的数据类型 – 数据类型可以省略(如上所示),因为可由编译器推断得出,称为“类型推断”

BinaryOperator<Long> bo = (Long x, Long y) -> {System.out.println("实现函数接口方法!");return x + y;
};
类型推断

所谓的“类型推断”是指:Lambda 表达式中的参数类型依赖于上下文环境,都是由编译器推断得出的。

在 Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型

总结

大致可分为三种类型:

  1. 格式为一种 – 无需参数,无返回值,Lambda 体一条语句即可
  2. 格式二、三为一种 – 需要一个参数,无返回值,可省略参数小括号,也是一条语句即可
  3. 格式四、五、六为一种 – 需要两种参数,有返回值,可以省略大括号与 return

参数类型均可省略,推荐使用一、二、五

2、函数式接口

何为函数式接口?

  • 函数式接口是指:只包含一个抽象方法的接口,该接口的对象可以通过 Lambda 表达式来创建。

    (若 Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)

  • 一般建议函数式接口上使用 @FunctionalInterface 注解修饰, 这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口

@FunctionalInterface 注解:用于标识该接口是函数式接口,防止在接口中添加多余的抽象方法。

不是所有的函数式接口都有 @FunctionalInterface 注解修饰,但是建议所有函数式接口都应该加上该注解,因为这有助于编译器检查该接口是否符合函数式接口的要求,即只有一个抽象方法。如果该注解标注在有多个抽象方法的接口上,则编译器会报错,提示该接口不符合函数式接口的定义。同时,加上该注解也可以让其他开发者更容易地理解该接口的用途和特点。

什么是抽象方法?

抽象方法指的是:没有实现代码的方法,只有方法声明(包括方法名、返回值类型、参数列表和异常列表)。在 Java 中,如果一个类中包含了抽象方法,那么这个类必须被声明为抽象类,因为抽象方法无法被直接调用。只有当一个类继承了抽象类并实现了抽象方法,才能创建该类的对象并调用该方法。

抽象方法的语法格式为:在方法声明中使用 abstract 关键字修饰方法,方法体中不包含实现代码。例如:

public abstract void draw();

抽象方法的作用是:为了让子类实现自己的方法逻辑,从而实现多态性和代码复用。在设计模式中也经常使用抽象方法来定义基础框架和算法流程,具体实现由子类来完成。

在函数式接口中,由于该接口只包含一个抽象方法,因此该方法默认为抽象方法,不需要显式使用 abstract 关键字进行修饰。

代码示例:

@FunctionalInterface
public interface MyInterface {void myMethod();
}
MyInterface myInterface = () -> System.out.println("Hello World!");
myInterface.myMethod(); // 输出:Hello World!

自定义函数式接口

只要方法的参数是函数式接口都可以用 Lambda 表达式

@FunctionalInterface
public interface MyNumber {double getValue();
}// 在函数式接口中使用泛型
public interface MyFunc<T> {T getValue(T t);
}// 作为参数传递 Lambda 表达式
public String toUpperString(MyFunc<String) mf, String str) {return mf.getValue(str);
}String newStr = toUpperString((str) -> str.toUpperCase(), "abcdef"); // 该 Lambda 表达式的作用是将字符串转换成大写字母并返回
System.out.println(newStr); // "ABCDEF"

作为参数传递 Lambda 表达式:

为了将 Lambda 表达式作为参数传递,接收 Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。

例如一个计算器接口,可以定义如下的函数式接口:
@FunctionalInterface
public interface Calculator {int calculate(int x, int y);
}

在该接口中,定义了一个抽象方法 calculate(int x, int y),接受两个整型参数 xy ,返回一个整型结果。该接口标记了 @FunctionalInterface 注解,表示该接口是一个函数式接口。 然后,可以使用 Lambda 表达式来创建该接口的实例,例如:

Calculator add = (x, y) -> x + y;
Calculator subtract = (x, y) -> x - y;
Calculator multiply = (x, y) -> x * y;
Calculator divide = (x, y) -> x / y;

在这个例子中,分别使用 Lambda 表达式创建了加法、减法、乘法、除法四种计算器操作的实例,这些实例都是 Calculator 函数式接口的实例。 然后,可以使用这些实例进行计算,例如:

int result = add.calculate(2, 3); // 加法运算,结果为 5

在这个例子中,使用加法操作的实例 add 对参数 2 和 3 进行了加法运算,并将结果赋值给 result 变量。

总之,自定义函数式接口可以根据具体的业务需求定义,然后使用 Lambda 表达式来创建该接口的实例,从而实现对业务逻辑的封装和复用。

Java 内置四大核心函数式接口

四种:消费型接口、供给型接口、函数型接口、断定型接口

1. Consumer 接口 – 消费型接口

Consumer 接口定义了一个接受一个泛型参数并返回 void 的操作。该接口有一个抽象方法 accept(T t),接收一个泛型参数 T,表示该操作的输入参数,无返回值。

源码解析:

package java.util.function;import java.util.Objects;@FunctionalInterface
public interface Consumer<T> {/*** 对给定的参数执行操作** @param t 要执行操作的参数*/void accept(T t);/*** 返回一个组合了多个Consumer的新Consumer,表示执行顺序为该Consumer接口先执行,然后执行after中的Consumer接口** @param after 要执行的另一个Consumer接口* @return 组合后的新Consumer接口*/default Consumer<T> andThen(Consumer<? super T> after) {Objects.requireNonNull(after);return (T t) -> {accept(t);after.accept(t);};}
}

在这个源码中,首先使用 @FunctionalInterface 注解标记了该接口是一个函数式接口。然后定义了一个抽象方法accept(T t),表示对给定的参数执行操作,并无返回值。接着定义了一个默认方法 andThen(Consumer<? super T> after),用于组合多个 Consumer 实例,表示执行顺序为该 Consumer 接口先执行,然后执行 after 中的Consumer 接口。

总之,Consumer接口可以用于对给定的参数执行操作,例如打印、修改状态等。该接口提供了一个默认方法 andThen 可以用于组合多个 Consumer 实例,从而实现更加复杂的操作。

应用场景:

Consumer 接口可以用于对集合进行遍历、对异步任务结果进行处理、以及对对象状态进行修改等场景中。使用Consumer 接口可以使代码更加简洁、易于维护,增强代码的可读性和可重用性。

以下是一些常见的场景:

  1. 集合遍历:可以使用 Consumer 接口对集合中的每个元素进行操作。

    例如对一个字符串列表中的每个字符串进行大写转换并输出:

List<String> list = Arrays.asList("apple", "banana", "orange");
Consumer<String> toUpperCase = s -> System.out.println(s.toUpperCase());
list.forEach(toUpperCase);

在这个例子中,定义了一个字符串列表 list,其中包含三个字符串元素。然后定义了一个 Consumer 实例 toUpperCase,用于将字符串转换为大写并输出。接着使用 forEach 方法遍历 list 中的每个元素,并将 toUpperCase 实例作为参数传入,实现对每个字符串的大写转换和输出操作。

  1. 异步任务处理:可以使用 Consumer 接口对异步任务的结果进行处理。

    例如对一个异步任务的结果进行打印:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello, World!");
Consumer<String> printResult = s -> System.out.println(s);
future.thenAccept(printResult); // "Hello, World!"

在这个例子中,使用 CompletableFuture 创建一个异步任务,该任务返回一个字符串 "Hello, World!"。然后定义了一个 Consumer 实例 printResult,用于打印异步任务的结果。使用 thenAccept 方法注册了一个 Consumer 类型的回调函数,该回调函数的作用是在 future 对象执行完毕后,将结果传递给 printResult Lambda 表达式并输出。表示当异步任务完成时对结果进行处理,体现了异步任务执行完成后的回调机制。

  1. 状态修改:可以使用 Consumer 接口对对象的状态进行修改。

    例如修改一个订单对象的状态:

public class Order {private String status;// getter和setter方法// ...
}
Consumer<Order> updateStatus = o -> o.setStatus("已发货");
Order order = new Order();
updateStatus.accept(order);

在这个例子中,定义了一个 Order 类,其中包含一个状态属性 status。然后定义了一个 Consumer 实例 updateStatus,用于将订单状态修改为 "已发货"。接着创建了一个订单对象 order,并将 updateStatus 实例作为参数传入,实现对订单状态的修改。

2. Supplier 接口 – 供给型接口

Supplier 接口产生给定泛型类型的结果。与 Function 接口不同,Supplier 接口不接受参数。

该接口有一个抽象方法 get(),用于返回一个泛型参数 T,表示该操作的结果。

源码解析:

package java.util.function;@FunctionalInterface
public interface Supplier<T> {/*** 获取一个结果** @return 表示操作结果的泛型参数T*/T get();
}

在这个源码中,首先使用 @FunctionalInterface 注解标记了该接口是一个函数式接口。然后定义了一个抽象方法 get(),用于获取一个结果并返回一个泛型参数 T。该方法没有参数,仅返回一个表示操作结果的泛型参数。

总之,Supplier 接口主要用于提供一个泛型参数类型的结果,例如获取当前时间、生成随机数、从数据库中获取数据等。Supplier 接口的 get() 方法没有参数,返回一个表示操作结果的泛型参数,可以与其它函数式接口进行组合使用,实现更加复杂的操作。

应用场景:

用于提供一个泛型参数类型的结果,例如生成随机数、从数据库中获取数据、获取当前时间等场景中。

  1. 生成随机数
Supplier<Integer> randomSupplier = () -> new Random().nextInt(100);
int randomNum = randomSupplier.get();
  1. 从数据库中获取数据
public class SupplierExample {public static void main(String[] args) {Supplier<List<String>> supplier = () -> {List<String> list = new ArrayList<>();try {ResultSet resultSet = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password").createStatement().executeQuery("select * from user");while (resultSet.next()) {list.add(resultSet.getString("name"));}} catch (SQLException e) {e.printStackTrace();}return list;};List<String> result = supplier.get();System.out.println(result.stream().collect(Collectors.joining(", ")));}
}
  1. 获取当前时间
Supplier<LocalDateTime> timeSupplier = LocalDateTime::now;
LocalDateTime currentTime = timeSupplier.get();
3. Function 接口 – 函数型接口

Function 接口接收一个参数并生成结果。默认方法可用于将多个函数链接在一起(compose, andThen):

package java.util.function;import java.util.Objects;@FunctionalInterface
public interface Function<T, R> {//将Function对象应用到输入的参数上,然后返回计算结果。R apply(T t);//将两个Function整合,并返回一个能够执行两个Function对象功能的Function对象。default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {Objects.requireNonNull(before);return (V v) -> apply(before.apply(v));}//返回一个由原始Function执行完后再执行after Function的新Functiondefault <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {Objects.requireNonNull(after);return (T t) -> after.apply(apply(t));}//接收任意类型参数,并返回相同类型参数的Function//作用:快速创建一个无需处理参数的Function实例static <T> Function<T, T> identity() {return t -> t;}
}

应用场景:

Function 接口主要用于将一个类型的值转化为另一个类型的值。它接受一个参数并返回一个结果,通常用于在数据处理、转换或映射过程中使用。

  1. 数据转换和映射
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);
backToString.apply("123");     // "123"
  1. 数据过滤和筛选
List<String> list = Arrays.asList("apple", "banana", "orange", "grape");
Function<String, Boolean> filterFunction = s -> s.startsWith("a");
List<String> filteredList = list.stream().filter(filterFunction::apply).collect(Collectors.toList());
System.out.println(filteredList); // [apple]
  1. 数据处理和转换
List<String> list = Arrays.asList("apple", "banana", "orange", "grape");
Function<String, Integer> mapFunction = String::length;
List<Integer> lengthList = list.stream().map(mapFunction::apply).collect(Collectors.toList());
System.out.println(lengthList); // [5, 6, 6, 5]
4. Predicate 接口 – 断定型接口

Predicate 接口是只有一个参数的返回布尔类型值的 断言型 接口。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非):

源码解析:

package java.util.function;
import java.util.Objects;@FunctionalInterface
public interface Predicate<T> {// 该方法是接收一个传入类型,返回一个布尔值.此方法应用于判断.boolean test(T t);//and方法与关系型运算符"&&"相似,两边都成立才返回truedefault Predicate<T> and(Predicate<? super T> other) {Objects.requireNonNull(other);return (t) -> test(t) && other.test(t);}// 与关系运算符"!"相似,对判断进行取反default Predicate<T> negate() {return (t) -> !test(t);}//or方法与关系型运算符"||"相似,两边只要有一个成立就返回truedefault Predicate<T> or(Predicate<? super T> other) {Objects.requireNonNull(other);return (t) -> test(t) || other.test(t);}// 该方法接收一个Object对象,返回一个Predicate类型.用于判断两个对象是否相等static <T> Predicate<T> isEqual(Object targetRef) {return (null == targetRef)? Objects::isNull: object -> targetRef.equals(object);}

应用场景:

Predicate 接口可以用于对集合进行过滤、对方法参数进行校验、以及实现复杂的逻辑判断等场景中。使用Predicate 接口可以使代码更加简洁、易于维护,增强代码的可读性和可重用性。

以下是一些常见的场景:

  1. 集合过滤:可以使用 Predicate 接口对集合进行过滤,例如筛选出所有大于等于 10 的整数:
List<Integer> list = Arrays.asList(1, 5, 10, 15, 20);
Predicate<Integer> predicate = i -> i >= 10;
List<Integer> filteredList = list.stream().filter(predicate).collect(Collectors.toList());

在这个例子中,首先创建了一个整型列表 list,然后定义了一个 Predicate 实例 predicate,用于判断一个整数是否大于等于 10。接着使用 stream 对列表进行流操作,在流操作中使用 filter 方法传入 predicate 实例对整数进行过滤,最后将过滤结果收集到一个新的列表中。

  1. 参数校验:可以使用 Predicate 接口对方法参数进行校验,例如校验一个字符串是否为空:
public void doSomething(String str) {Predicate<String> isEmpty = s -> s == null || s.length() == 0;if (isEmpty.test(str)) {throw new IllegalArgumentException("字符串不能为空");}// ...
}

在这个例子中,定义了一个 Predicate 实例 isEmpty,用于判断一个字符串是否为空。在方法中,对传入参数str进行校验,如果为空则抛出异常。

  1. 复杂逻辑判断:可以使用 Predicate 接口对多个条件进行组合,实现更加复杂的逻辑判断。例如判断一个字符串是否包含数字和大写字母:
String str = "Hello, World! 123";
Predicate<String> containsDigit = s -> s.matches(".*\\d.*");
Predicate<String> containsUpperCase = s -> s.matches(".*[A-Z].*");
if (containsDigit.and(containsUpperCase).test(str)) {System.out.println("字符串符合要求");
}

在这个例子中,定义了两个 Predicate 实例 containsDigitcontainsUpperCase,分别用于判断一个字符串是否包含数字和大写字母。在判断中使用 and 方法将两个实例组合起来,表示字符串必须同时包含数字和大写字母才符合要求。最后使用 test 方法对字符串进行判断,如果符合要求则输出相应信息。

小结
  1. Consumer 接口

    • 抽象方法 void accept(T t); 接收一个泛型参数 T,表示该操作的输入参数,无返回值;

    • 一个默认方法 andThen(Consumer<? super T> after),用于组合多个 Consumer 实例,表示执行顺序为- 该 Consumer 接口先执行,然后执行 after 中的Consumer 接口。

  2. Supplier 接口

    • 抽象方法 T get; 用于返回一个泛型参数 T,表示该操作的结果。
  3. Function 接口

    • 抽象方法 R apply(T t); 接收参数 T 然后返回计算结果;

    • 两个默认方法(compose, andThen),用于将多个函数链接在一起;

    • 一个静态方法 identity() ,用于快速创建一个无需处理参数的 Function 实例。

  4. Predicate 接口

    • 抽象方法 boolean test(T t); 返回一个布尔值,应用于判断。

    • 包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与(and),或(or),取反(negate))

    • 一个静态方法 isEqual,用于判断两个对象是否相等

其他接口

在这里插入图片描述

3、方法引用与构造器引用

概念

使用操作符 “::” 将方法名和对象或类的名字分隔开来。

使用场景

当要传递给 Lambda 体的操作,已经有实现的方法了,可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!)

(一)方法引用

1. 类 :: 静态方法

类 :: 静态方法(ClassName::staticMethodName

在这里插入图片描述

Function<String, Integer> strToInt = Integer::parseInt;
int result = strToInt.apply("123");
2. 对象 :: 实例方法

对象 :: 实例方法(instance::instanceMethodName

在这里插入图片描述

List<String> list = Arrays.asList("apple", "banana", "orange", "grape");
list.forEach(System.out::println);
3. 类 :: 实例方法

类 :: 实例方法(ClassName::instanceMethodName

在这里插入图片描述

List<String> list = Arrays.asList("apple", "banana", "orange", "grape");
list.stream().map(StringUtils::toUppercase).forEach(System.out::println);

(二)构造器引用

格式:ClassName::new

与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义的方法,与构造器参数列表要与接口中抽象方法的参数列表一致。

在这里插入图片描述

Function<String, Person> createPerson = Person::new;
Person person = createPerson.apply("Tom"); 

(三)数组引用

格式:type[] :: new

在这里插入图片描述

// 创建一个长度为 10 的数组
Function<Integer, String[]> createArray = String[]::new;
String[] array = createArray.apply(10);
// 将其中所有元素设置为 0
Arrays.stream(arr).forEach(int[]::setAll);

4、强大的 Stream API

概念

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数 据库查询。也可以使用 Stream API 来并行执行操作。简而言之, Stream API 提供了一种高效且易于使用的处理数据的方式。

流(Stream)到底是什么?

是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

“集合讲的是数据,流讲的是计算!”

注意:

  1. Stream 自己不会存储元素。
  2. Stream 不会改变源对象。相反,他们会返回一个持有结果的新 Stream。
  3. Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

操作三步骤

在这里插入图片描述

(一)创建 Stream
顺序流与并行流

Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:

  • default Stream stream() : 返回一个顺序流

  • default Stream parallelStream() : 返回一个并行流

并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。

Java8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换。

数组流

Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

static Stream stream(T[] array): 返回一个流

重载形式,能够处理对应基本类型的数组:

  • public static IntStream stream(int[] array)
  • public static LongStream stream(long[] array)
  • public static DoubleStream stream(double[] array)
由值创建流

可以使用静态方法 Stream.of(), 通过显示值创建一个流。它可以接收任意数量的参数。

public static Stream of(T… values) : 返回一个流

无限流

由函数创建流。可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。

  • 迭代

    public static Stream iterate(final T seed, final UnaryOperator f)

  • 生成

    public static Stream generate(Supplier s)

(二)Stream 的中间操作
什么是中间操作?

中间操作是对 Stream 对象进行加工处理的操作,用于对 Stream 中的元素进行处理、筛选、过滤、排序、去重等操作。中间操作可以使用链式调用的方式进行连续操作,**每次操作都会返回一个新的 Stream 对象,**这样可以构建出一条操作流水线。

惰性求值

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值” 。

API
筛选与切片
方法描述
filter(Predicate)接收 Lambda ,从流中排除某些元素
distinct()筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
limit(long maxSize)截断流,使其元素不超过给定数量。
skip(long n)跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
映射
方法描述
map(Function f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
mapToDouble(ToDoubleFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。
mapToInt(ToIntFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
mapToLong(ToLongFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另 一个流,然后把所有流连接成一个流。
排序
方法描述
sorted()产生一个新流,其中按自然顺序排序
sorted(Comparator comp)产生一个新流,其中按比较器顺序排序
(三)终止操作
何为终止操作?
  • 终止操作是对 Stream 对象执行最终操作的操作,用于触发 Stream 的处理流程,并返回最终的结果。
  • 终止操作可以是有返回值的,例如collect()方法,也可以是没有返回值的,例如forEach()方法。
  • 终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void。
API
查找与匹配
方法描述
allMatch(Predicate p)检查是否匹配所有元素
anyMatch(Predicate p)检查是否至少匹配一个元素
noneMatch(Predicate p)检查是否没有匹配所有元素
findFirst()返回第一个元素
count()返回流中元素总数
max(Comparator c)返回流中最大值
min(Comparator c)返回流中最小值
forEach(Consumer)内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代一一它帮你把迭代做了)
归约
方法描述
reduce(T iden,BinaryOperator)可以将流中元素反复结合起来,得到一个值返口 T
reduce(BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返 Optional<T>

**备注:**map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名

收集
方法描述
collect(Collector c)将流转换为其他形式。接收一个 Collector 接口的实现,用于给 Stream 中元素做汇总的方法

Collector 接口中方法的实现决定了如何对流执行收集操作(如收 集到 List、Set、Map)。
但是 Collectors 实用类提供了很多静态 方法,可以方便地创建常见收集器实例,具体方法与实例如下表:

在这里插入图片描述在这里插入图片描述

ForK/Join 框架

什么是 Fork/Join 框架?

**Fork/Join 框架:**就是在必要的情况下,将一个大任务,进行拆分 (fork) 成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。

并行流:

parallelStream 可多线程执行,是基于 ForkJoin 框架实现的

forEach() 用到的就是多线程

在这里插入图片描述

Fork/Join 框架与传统线程池的区别

采用“工作窃取”模式(work-stealing):当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中窃取一个并把它放在自己的队列中继续执行。

相对于一般的线程池实现,fork/join 框架的优势体现在对其中包含的任务的处理方式上

  • 在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。

  • 而在 fork/join 框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行,这种方式是动态的,减少了线程的等待时间,并提高了性能。

  • Fork/Join 框架提供了一些特殊的方法,比如 fork()join() 等,用于处理分治任务,简化了代码的编写和管理。

5、新时间日期 API

LocalDate、LocalTime、LocalDateTime

三种类的实例都是不可变的对象。

  • LocalDate – 表示使用 ISO-8601 日历系统的日期
  • LocalTime – 表示使用 ISO-8601 日历系统的时间
  • LocalDateTime – 表示使用 ISO-8601 日历系统的日期和时间
LocalDateTime.class //日期+时间 format: yyyy-MM-ddTHH:mm:ss.SSS
LocalDate.class //日期 format: yyyy-MM-dd
LocalTime.class //时间 format: HH:mm:ss

注:ISO-8601 日历系统是国际标准化组织制定的现代公民的日期和时间的表示。

各种方法如下:

在这里插入图片描述

instant 时间戳

用于“时间戳”的运算。它是以 Unix 元年(传统的设定为 UTC 时区 1970 年 1 月 1 日午夜时分)开始所经历的描述进行运算。

Duration 和 Period

  • Duration: 用于计算两个**“时间”间隔**

    import java.time.Duration;
    import java.time.LocalDateTime;
    public class DurationExample {public static void main(String[] args) {//获取当前时间LocalDateTime start = LocalDateTime.now();System.out.println("开始时间:" + start);//模拟程序执行try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}//获取执行结束时间LocalDateTime end = LocalDateTime.now();System.out.println("结束时间:" + end);//计算时间差Duration duration = Duration.between(start, end);System.out.println("程序执行时间:" + duration.toMillis() + " 毫秒");}
    }
    
  • Period: 用于计算两个**“日期”间隔**

    import java.time.LocalDate;
    import java.time.Period;
    public class PeriodExample {public static void main(String[] args) {//获取当前日期LocalDate start = LocalDate.now();System.out.println("开始日期:" + start);//模拟时间间隔LocalDate end = start.plusDays(30);System.out.println("结束日期:" + end);//计算日期差Period period = Period.between(start, end);System.out.println("日期间隔:" + period.getDays() + " 天");}
    }
    

操作日期的类

  • TemporalAdjuster: 时间校正器。

例如,有时我们可能需要获取:将日期调整到“下个周日”等操作。

  • TemporalAdjusters: 该类通过静态方法提供了大量的常用 TemporalAdjuster 的实现,方便开发者对日期进行各种调整操作。
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
public class TemporalAdjusterExample {public static void main(String[] args) {//获取当前日期LocalDate date = LocalDate.now();System.out.println("当前日期:" + date);//获取本月的第一天LocalDate firstDayOfMonth = date.with(TemporalAdjusters.firstDayOfMonth());System.out.println("本月第一天:" + firstDayOfMonth);//获取本月的最后一天LocalDate lastDayOfMonth = date.with(TemporalAdjusters.lastDayOfMonth());System.out.println("本月最后一天:" + lastDayOfMonth);//获取下一个周二LocalDate nextTuesday = date.with(TemporalAdjusters.next(DayOfWeek.TUESDAY));System.out.println("下一个周二:" + nextTuesday);}
}

日期时间格式化和解析的类

java.time.format.DateTimeFormatter 类:

该类提供了三种格式化方法:

1. 预定义的标准格式

DateTimeFormatter 类提供了一些预定义的标准格式,例如 ISO_DATEISO_TIMEISO_DATE_TIME 等等,这些标准格式已经定义好了日期时间的格式,开发者可以直接使用。

2. 语言环境相关的格式

DateTimeFormatter 类还提供了一些与语言环境相关的格式,例如 ofLocalizedDate(FormatStyle style)ofLocalizedTime(FormatStyle style)ofLocalizedDateTime(FormatStyle style) 等等,这些格式会根据不同的语言环境自动适配不同的日期时间格式

3. 自定义的格式

例如 ofPattern(String pattern) 方法可以传入一个字符串参数,用于定义自己想要的日期时间格式,"yyyy-MM-dd HH:mm:ss" 就是一个自定义的日期时间格式。

时区的处理

Java8 中加入了对时区的支持,带时区的时间为分别为:ZonedDateZonedTimeZonedDateTime 其中每个时区都对应着 ID,地区 ID 都为 “{区域}/{城市}”的格式 例如 :Asia/Shanghai 等

  • ZoneId:该类中包含了所有的时区信息
  • getAvailableZoneIds(): 可以获取所有时区时区信息
  • of(id): 根据指定的时区信息获取 ZoneId 对象

讲一下与传统日期的转换

Java8 中的日期和时间 API 与传统的日期类(如 java.util.Datejava.util.Calendar)之间存在一些差异,因此在进行日期转换时需要注意以下几点:

  1. java.util.Datejava.util.Calendar 类是可变的,而 Java8 中的日期和时间类是不可变的,因此在进行转换时需要特别注意。

  2. java.util.Date 类的精度是毫秒级别,而 Java8 中的日期和时间类的精度可以达到纳秒级别,因此在进行转换时需要注意精度的损失。

  3. Java8 中的日期和时间类之间可以相互转换,例如可以将 LocalDateTime 对象转换为 Instant 对象,但是需要注意转换后的时区和精度问题。

    以下是一个简单的 Java8 和传统日期类(java.util.Datejava.util.Calendar)之间的转换示例:

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Calendar;
import java.util.Date;
public class DateConversionExample {public static void main(String[] args) {// Java 8日期类转换为传统日期类LocalDateTime dateTime = LocalDateTime.now();Date date = Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());Calendar calendar = Calendar.getInstance();calendar.setTime(date);System.out.println("Java 8日期类转换为传统日期类:" + calendar.getTime());// 传统日期类转换为Java 8日期类calendar = Calendar.getInstance();calendar.set(2022, Calendar.APRIL, 28);date = calendar.getTime();dateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());System.out.println("传统日期类转换为Java 8日期类:" + dateTime);// Java 8日期类之间的转换dateTime = LocalDateTime.now();Instant instant = dateTime.atZone(ZoneId.systemDefault()).toInstant();dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());System.out.println("Java 8日期类之间的转换:" + dateTime);}
}

6、接口中的默认方法与静态方法

概念

Java8 中允许接口中包含具有具体实现的方法,该方法称为 “默认方法”,默认方法使用 default 关键字修饰;并允许添加静态方法。

接口默认方法的“类优先”原则:

如果一个类实现了多个接口,而这些接口中定义了同名的默认方法,那么编译器就会产生歧义,因为它不知道应该使用哪个默认方法。为了解决这个问题,Java 8 引入了“类优先”(Class Priority)的原则,即如果一个类继承了另一个类并且实现了一个接口,而这两者中都定义了同名的默认方法,那么类中的方法优先于接口中的方法。

注意:在使用“类优先”原则时,如果类中的方法不是完全覆盖了接口中的方法,那么编译器会报错,此时需要在类中显式地调用接口中的方法。

interface MyInterface {default void myMethod() {System.out.println("Default method in interface");}
}
class MyClass {public void myMethod() {System.out.println("Method in class");}
}
class MyImplementation extends MyClass implements MyInterface {
}
public class DefaultMethodExample {public static void main(String[] args) {MyImplementation obj = new MyImplementation();obj.myMethod(); // 输出 "Method in class"}
}

7、其他新特性

Optional 类?

Optional 类 (java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且 可以避免空指针异常

常用方法:

  1. Optional.of(T t): 创建一个 Optional 实例
  2. Optional.empty(): 创建一个空的 Optional 实例
  3. Optional.ofNullable(T t): 若 t 不为 null,创建 Optional 实例,否则创建空实例
  4. isPresent(): 判断是否包含值
  5. orElse(T t): 如果调用对象包含值,返回该值,否则返回 t
  6. orElseGet(Supplier s): 如果调用对象包含值,返回该值,否则返回 s 获取的值
  7. map(Function f): 如果有值对其处理,并返回处理后的 Optional,否则返回 Optional.empty()
  8. flatMap(Function mapper): 与 map 类似,要求返回值必须是 Optional

重复注解与类型注解

Java8 对注解处理提供了两点改进:可重复的注解可用于类型的注解

  1. 可重复的注解

    在Java 8之前,同一个注解不能在同一个地方重复使用,这在某些情况下会导致代码的冗余。Java8 引入了可重复的注解,它允许同一个注解在同一个地方重复使用。这种注解需要使用 @Repeatable 元注解来标注

    举个例子,假设我们有一个 @Author 注解,用于表示文章的作者。在 Java8 之前,我们无法在同一个类或方法上使用多次 @Author 注解,而必须定义多个注解,如 @Author1@Author2 等。在 Java8 中,我们可以使用可重复的注解来解决这个问题,示例代码如下:

    @Repeatable(Authors.class)
    @interface Author {String name();
    }
    @interface Authors {Author[] value();
    }
    @Authors({@Author(name = "Alice"),@Author(name = "Bob")
    })
    public class Article {// ...
    }
    
  2. 可用于类型的注解

    Java8 还引入了一种新的注解类型 —— 类型注解(Type Annotation),它可以用于注解类型(如类、接口、枚举、注解等)上。这种注解需要使用新的元注解 @Target 来标注,同时需要指定 ElementType.TYPE_USE 作为注解的目标类型。

    举个例子,假设我们有一个 @NotNull 注解,用于表示一个类型不为 null。在 Java8 之前,我们只能将它应用到方法的参数上,而不能将其应用到类型上。在 Java8 中,我们可以使用类型注解将其应用到类型上,示例代码如下:

    interface NotNull {}
    class Example<@NotNull T> {public void foo(List<@NotNull String> strings) {}
    }
    

8、Java 8 实战

推荐参考此篇文章:

https://javaguide.cn/java/new-features/java8-common-new-features.html

9、peek()

peek() 方法是 Stream API 中的一个中间操作,它允许你在不改变数据的情况下,对流中的元素进行某种操作。peek() 方法接收一个 Consumer 接口的实现,对流中的每个元素执行该操作,并返回一个新的流,其中包含与原始流相同的元素。

import java.util.stream.Stream;public class Main {public static void main(String[] args) {Stream.of(1, 2, 3, 4, 5).filter(n -> n % 2 == 0).peek(System.out::println).collect(Collectors.toList());}
}

在这个示例中,我们创建了一个包含 1 到 5 的整数流,然后使用 filter() 方法过滤出偶数。接下来,我们使用 peek() 方法打印每个元素,最后使用 collect() 方法将流转换为列表。输出结果如下:

2
4

返回一个新的流有啥必要?

返回一个新的流在某些情况下是有必要的,因为这样可以保留原始流的状态。当你对一个流进行操作时,例如过滤、映射或排序等,原始流不会受到影响,因为这些操作都是惰性的,只有在需要时才会执行。

然而,如果你在 forEach 方法中对元素进行了修改,那么这些修改会影响到原始流。这是因为 forEach 方法返回的是一个 void,它不能直接返回一个新的流。但是,你仍然可以在 forEach 方法中对原始流进行操作,然后使用 .collect() 方法将流转换为列表或其他集合类型。

总之,返回一个新的流并不总是必要的,但它在某些情况下是有用的,特别是当你需要保留原始流的状态时。

应用场景

peek() 方法主要用于调试。它可以用于查看流中的元素,但不会改变流的状态。例如,你可以使用 peek() 方法来查看一个整数流中的偶数,但不会将它们从流中删除。这个方法在多线程的场景下也很有用,因为它可以用于读取队列头部元素。

总之,peek() 方法主要用于调试和多线程场景下读取队列头部元素。
html](https://javaguide.cn/java/new-features/java8-common-new-features.html)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/212065.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

开发猿的平平淡淡周末---2023/12/10

天气阴 温度适宜17摄氏度 AM 昨晚竟然下小雨了&#xff0c;还好还好&#xff0c;昨天刷的两个背包基本干了 一觉睡到日三竿&#xff0c;谁是神仙&#xff0c;我是神仙&#xff08;哈哈哈哈哈哈&#xff09; 刷会儿视频 补充下起床的动力 洗漱&#xff0c;恰饭&#xff0c;肝…

电工--基本放大电路

电压放大倍数、输入电阻和输出电阻是放大电路的三个主要性能指标 共发射极基本交流放大电路 晶体管&#xff1a;电流放大作用。能量较小的输入信号通过晶体管的控制作用&#xff0c;去控制电源所共给的能量&#xff0c;以在输出端获得一个能量较大的信号 集电极电源电压&#…

电子学会C/C++编程等级考试2021年12月(五级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:书架 John最近买了一个书架用来存放奶牛养殖书籍,但书架很快被存满了,只剩最顶层有空余。 John共有N头奶牛(1 ≤ N ≤ 20,000),每头奶牛有自己的高度Hi(1 ≤ Hi ≤ 10,000),N头奶牛的总高度为S。书架高度为B(1 ≤ B ≤ S &…

[LeetCode周赛复盘] 第 375 场周赛20231210

[LeetCode周赛复盘] 第 375 场周赛20231210 一、本周周赛总结100143. 统计已测试设备1. 题目描述2. 思路分析3. 代码实现 100155. 双模幂运算1. 题目描述2. 思路分析3. 代码实现 100137. 统计最大元素出现至少 K 次的子数组1. 题目描述2. 思路分析3. 代码实现 100136. 统计好分…

Linux用户和权限

一、认知root用户 1.1 了解什么是root用户&#xff08;超级管理员&#xff09; root用户&#xff08;超级管理员&#xff09; 无论是Windows、MacOS、Linux均采用多用户的管理模式进行权限管理。 在Linux系统中&#xff0c;拥有最大权限的账户名为&#xff1a;root&#x…

Java9及之后关于类加载器的新特性

为了保证兼容性&#xff0c;JDK9没有从根本上改变三层类加载器的架构和双亲委派模型&#xff0c;但为了模块化系统的顺利运行&#xff0c;仍然发生了一些值得被注意的变动。 一、变动1 由于引入了模块化概念&#xff0c;所以不同的类加载器回去加载属于不同模块的类 启动类加…

Nginx负载均衡实战

&#x1f3b5;负载均衡组件 ngx_http_upstream_module https://nginx.org/en/docs/http/ngx_http_upstream_module.html upstream模块允许Nginx定义一组或多组节点服务器组&#xff0c;使用时可以通过多种方式去定义服务器组 样例&#xff1a; upstream backend {server back…

从零开发短视频电商 在AWS SageMaker已创建的模型列表中进行部署

1.导航到 SageMaker 控制台。 2.在 SageMaker 控制台的左侧导航栏中&#xff0c;选择 “模型” 选项。 3.在模型列表中&#xff0c;找到您要部署的模型。选择该模型。 4.点击 “创建端点” 选项或者点击 “创建端点配置” 选项都可以进行部署。 选择创建端点进去后还是会进行…

[CTFshow 红包挑战] 刷题记录

文章目录 红包挑战7红包挑战8红包挑战9 红包挑战7 考点&#xff1a;xdebug拓展 源码 <?php highlight_file(__FILE__); error_reporting(2);extract($_GET); ini_set($name,$value);system("ls ".filter($_GET[1])."" );function filter($cmd){$cmd s…

1832_org-mode的注释处理

Grey # :OPTIONS ^:nil org-mode的注释处理 关于这部分其实比较简单&#xff0c;在我现在的使用诉求上来说要求不多。但是我觉得如果考虑以后把文学式编程作为一种开发的主要体验的话&#xff0c;掌握这样的操作很有必要。因为我可以控制部分信息的输出。 自然&#xff0c;控…

LeetCode-数组-重叠、合并、覆盖问题-中等难度

435. 无重叠区间 我认为区间类的题型&#xff0c;大多数考验的是思维能力&#xff0c;以及编码能力&#xff0c;该类题型本身并无什么算法可言&#xff0c;主要是思维逻辑&#xff0c;比如本题实际上你只需要能够总结出重叠与不重叠的含义&#xff0c;再加上一点编码技巧&#…

Verilog基础:$random系统函数的使用

相关阅读 Verilog基础​编辑https://blog.csdn.net/weixin_45791458/category_12263729.html $random系统函数语法的BNF范式如下所示&#xff0c;有关BNF范式相关内容&#xff0c;可以浏览以往文章Verilog基础&#xff1a;巴科斯范式(BNF)。 $random系统函数在每次调用时返回一…

【IDEA】IntelliJ IDEA中进行Git版本控制

本篇文章主要记录一下自己在IntelliJ IDEA上使用git的操作&#xff0c;一个新项目如何使用git进行版本控制。文章使用的IDEA版本 IntelliJ IDEA Community Edition 2023.3&#xff0c;远程仓库为https://gitee.com/ 1.配置Git&#xff08;File>Settings&#xff09; 2.去Git…

[gRPC实现go调用go]

1什么是RPC RPC&#xff1a;Remote Procedure Call&#xff0c;远程过程调用。简单来说就是两个进程之间的数据交互。正常服务端的接口服务是提供给用户端(在Web开发中就是浏览器)或者自身调用的&#xff0c;也就是本地过程调用。和本地过程调用相对的就是&#xff1a;假如两个…

深度优先遍历(DFS)

时间复杂度与深搜一致&#xff1b;

STM32 定时器总结

缩写 ARR: Auto-Reload Register&#xff08;保存定时器的计数范围&#xff09; PSC: Prescaler register&#xff08;预分频器寄存器&#xff0c;根据设置的分频因子N&#xff0c;计数N个定时器时钟脉冲后&#xff0c;产生一个CNT计数&#xff0c;以此实现分频功能&#xff0…

Android渲染-AHardwareBuffer

本文主要从应用的角度介绍android的native层AHardwareBuffer创建纹理以及保存渲染数据。 HardwareBuffer 要介绍native层的AHardwareBuffer&#xff0c;就需要先从Java层的HardwareBuffer说起。Android官方对于HardwareBuffer介绍如下&#xff1a; HardwareBuffer wraps a na…

基于SSM的点餐系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

【已解决】解决UbuntuKali无法进行SSH远程连接

目录 Ubuntu20.04配置SSH远程连接Kali Linux配置SSH远程连接 Ubuntu20.04配置SSH远程连接 首先更新安装包 sudo apt-get update 下载SSH服务 sudo apt install openssh-server 查看SSH服务 service ssh status 打开 /etc/ssh/sshd_config文件修改配置文件 将PermitRootLog…

12.视图

目录 1.视图的含义与作用 2.视图的创建与查看 1.创建视图的语法形式 2、查看视图&#xff1a; 1.使用DESCRIBE语句查看视图基本信息 2.使用SHOW TABLE STATUS语查看视图基本信息查看视图的信息 3.使用SHOW CREATE VIEW语查看视图详细信息 4.在views表中查看视图详细信息…