Java中如何使用函数式接口和StreamAPI?
函数式编程
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提供了用于在集合和数组等数据上进行封装、筛选、计算的方法。有了它们,我们可以轻松快捷地操作集合和数组等数据结构,提高编程效率同时提升代码质量,降低代码难度,增加代码的可读性。
