欢迎访问宙启技术站
智能推送

Java中如何使用函数式接口和StreamAPI?

发布时间:2023-06-19 03:02:45

函数式编程

Java作为一门静态语言,一直遵循着命令式编程的思想:通过给计算机一系列指令,使其步入一个确定的计算流程最终输出结果。这种方式的优势在于代码逻辑清晰可控,缺点则难以优化,更难抽象和重用代码; 与其不同,函数式编程将函数作为基本构造块,强调函数间的相互作用,利用 的函数计算避免了状态相关的问题。这种思想下的代码更为简洁、优雅、准确。Java 8以后引入了函数式编程的元素,包括Lambda表达式、方法引用、函数式接口等。

函数式接口

Java函数式编程的核心要素——函数式接口,这种接口不仅仅在语法层面上限制了函数接口中的抽象方法个数,更是在编译时候判定类型的内容上作了更为详细的限制。只要更新所有参数类型和返回类型相对应的函数接口中的抽象方法,函数式接口就允许将Lambda表达式作为参数传入方法,仅此一点就提升了编程效率和代码可读性。

1.常用函数式接口的定义

Java 8定义了四种函数式接口:Consumer、Supplier、Function、Predicate。尽管你不知道什么是函数式接口,但是你一定写过它们的实例。

Consumer

接受一个参数,不返回任何数据。例如:

Consumer<String> c = System.out::println; c.accept("foo"); // prints "foo"

Supplier

与Consumer相反,它不接受参数,但需要返回值。例如:

Supplier<String> s = () -> "Hello World!"; System.out.println(s.get()); // prints "Hello World!"

Function

接受一个参数,传递另一个类型的数据。例如:

Function<String, Integer> f = Integer::valueOf; Integer i = f.apply("42"); System.out.println(i); // prints "42"

Predicate

接受一个参数,返回布尔值。例如:

Predicate<String> p = s -> s.isEmpty(); System.out.println(p.test("foo")); // prints "false"

2. 自定义函数式接口

我们也可以自定义一个函数式接口以适应更具体的需求,它们有一些低级别的接口,如UnaryOperator和BinaryOperator。

UnaryOperator

继承自Function,只是各种类型的参数类型和返回类型相同。

BinaryOperator

继承自BiFunction,只是各种类型的参数类型和返回类型相同。

3. Lambda表达式

Lambda表达式是函数式接口中的最重要成分。 Lambda是一个临时方法,与传统的Java方法类似,但它实际上是一种更加简单,更加紧凑的语法。Lambda表达式可以像变量一样用来声明和定义各种接口中的 抽象方法。

Lambda的标准结构为:(parameters) -> expression或者(parameters) -> { statements; }。

下面是一些Lambda表达式的例子:

(number1, number2) -> number1 + number2

(String str) -> str.length()

(boolean b1, boolean b2) -> b1 == b2

4. Stream API

Java 8引入的另一个重要元素是Stream API,它可以运用在集合、数组等元素的处理上,并且可以在方法表达式中实现处理和操作。

Stream API提供一种易于理解和并行化的方法来处理数据。在应用程序中使用Java Stream可以实现以下:

- 迭代Stream的每个元素

- 排序

- 过滤Stream中的元素

- 利用Stream对数据组成分组,并创建统计结果

- 利用Stream对数据进行操作聚合等。

在Java 8中,我们可以通过Arrays类或Collection类中的stream()方法或parallelStream()方法创建一个Stream对象。

例如:

List<String> stringList = Arrays.asList("One", "Two", "Three", "Four");

//创建一个并行化的Stream

Stream<String> stream = stringList.parallelStream();

//创建一个非并行化的Stream

Stream<String> stream = stringList.stream();

除了创建Stream,Stream API还为JDK中的各种数据结构提供了许多经典的算子。我们将列出一些常用的:

1. forEach:迭代Stream中的每一项并执行操作。

List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);

nums.stream().forEach(System.out::println);

2. map:应用给定的函数到Stream的每一项,然后返回一个新的Stream,如下计算数字平方的例子:

List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);

nums.stream()

    .map(n -> n * n)

    .forEach(System.out::println);

3. filter:从Stream的所有项中过滤掉不符合断言条件的元素。如下是取排序后 个大于3的数的例子:

List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);

int first = nums.stream()

    .filter(n -> n > 3)

    .findFirst()

    .orElse(null);

    

4. limit:返回Stream中的前n项。

List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);

nums.stream()

    .limit(3)

    .forEach(System.out::println);

5. count:返回Stream中的元素数。

List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);

int count = nums.stream().count();

    

6. reduce:累加函数操作,将Stream中的所有项结合为一项。

List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);

int sum = nums.stream()

    .reduce(0, Integer::sum);

7. collect:将Stream转换成给定类型的数据结构。

List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);

List<Integer> evenNums = nums.stream()

    .filter(n -> n % 2 == 0)

    .collect(Collectors.toList());

以上是常用的Stream API算子,当然还有很多其他的算子供开发人员使用,可以根据具体需求自行查阅。

综上所述,Java 8中的函数式接口和Stream API提供了用于在集合和数组等数据上进行封装、筛选、计算的方法。有了它们,我们可以轻松快捷地操作集合和数组等数据结构,提高编程效率同时提升代码质量,降低代码难度,增加代码的可读性。