Java函数式编程(下)

简介: 我们从小学数学开始,就会慢慢形成函数的概念,简单的说,函数描述的就是自变量与因变量的映射关系。不管是在编程语言中,还是一个在系统设计中,都会用到函数思想来设计。它就像是一个黑箱子,我们在调用函数的时候,不用在乎函数内部的实现,只需要保证输入参数调用能返回想要的结果。运用函数思想去思考许多问题,都能简化模型,比如,调用一个接口或一个微服务,我只需关注传入的参数和返回正确的结果,而不需要过多关注内部实现。


三、收集器



前面我们已经讲过了流的聚合操作,在对流进行一系列操作后,最终再聚合成我们想要的数据结构,我们可以使用JDK提供的收集器,也可以自己定义一个收集器。


1. 定义收集器


定义一个收集器只需要实现 Collector 接口,然后使用流的 collect 方法来使用,下面我们自己模拟 Collectors.joining() 收集器,首先定义收集器:

public class StringCollector implements Collector<Object, StringJoiner, String> {
    @Override
    public Supplier<StringJoiner> supplier() {
        return () -> new StringJoiner(",", "[", "]");
    }
    @Override
    public BiConsumer<StringJoiner, Object> accumulator() {
        return ((stringJoiner, o) -> stringJoiner.add(o.toString()));
    }
    @Override
    public BinaryOperator<StringJoiner> combiner() {
        return StringJoiner::merge;
    }
    @Override
    public Function<StringJoiner, String> finisher() {
        return StringJoiner::toString;
    }
    @Override
    public Set<Characteristics> characteristics() {
        return Collections.emptySet();
     }
}   

使用上面定义的收集器:

Stream<Integer> stream = Stream.of(1, 2, 3, 4);
Stringstr = stream.collect(new StringCollector());
// str的值为:[1,2,3,4]


2. 自带收集器


2.1 toCollection

将流转换成集合。例如:

Stream<Integer> stream = Stream.of(1, 2, 3, 4);
List<Integer> collection = stream.collect(Collectors.toCollection(() -> new ArrayList<>()));
List<Integer> list = stream.collect(Collectors.toList());
Set<Integer> set = stream.collect(Collectors.toSet());

三个收集器:

  • toCollection:通过函数提供集合类型,可以用于自己封装的集合类型
  • toList:将流转换成List集合
  • toSet:将流转换成Set集合


2.2 toMap

将流转成Map,例如,已知一个班级的学生信息,将学生列表转换成以姓名为Key,学生信息为Value的Map:

Map<String, Student> map = students.stream()
        .collect(Collectors.toMap(Student::getName, Function.identity()));
2.3 joining

2.3 joining

将流中每个元素通过指定分隔符拼接成字符串。例如,已知一个班级的学生信息,将学生的姓名拼接成用逗号隔开的字符串:

String collect = students.stream()
        .map(Student::getName)
        .collect(Collectors.joining(","));

可以使用 joining 的另一个重载方法指定结果字符串的前缀和后缀。


2.4 mapping

将流中的元素类型转成成另一种类型,与 map 方法类似。例如,获取学生的姓名列表:

List<String> collect = students.stream()
        .collect(Collectors.mapping(Student::getName, Collectors.toList()));
//等效于
List<String> collect = students.stream()
       .map(Student::getName)
       .collect(Collectors.toList());
  • minBy 等效于 min
  • maxBy 等效于 max
  • counting 等效于 count
  • reducing 等效于 reduce

2.5 summingInt、summarizingInt、averagingInt

summingIntsummingLongsummingDouble 都是用来对相应类型的数据进行统计的,返回值对应 IntSummaryStatisticsLongSummaryStatisticsDoubleSummaryStatistics,它们都包含统计项:计数、最小值、最大值、平均值、总和。例如,已知一个班级的学生信息,统计学生的成绩:

DoubleSummaryStatistics statistics = students.stream()
        .collect(Collectors.summarizingDouble(Student::getScore));
// {count=6, sum=500.000000, min=59.000000, average=83.333333, max=95.000000}

averagingIntaveragingLongaveragingDouble 只是用来统计平均值。

summarizingIntsummarizingLongsummarizingDouble 只是用来统计总和。


2.6 groupingBy、partitioningBy

groupingBy 方法可以分组收集流数据,会生成一个以分组字段为Key的Map,如果要按自己定义的类分组,该类需要重写 hashCode 方法。例如,已知一个班级的学生信息,按性别分组:

Map<Boolean, List<Student>> collect = students.stream()
        .collect(Collectors.groupingBy(Student::getGender));

partitioningBy 是用于分区,功能和 groupingBy 类似,返回结果类型也一样,但分区方法的返回Map的Key只能是 Boolean 类型,所以结果只能分为两组。


2.8 collectingAndThen

collectingAndThen 方法可以对流进行聚合后,再添加后置操作,该方法第一个参数是收集器,第二个参数是 Function 函数,用于后置操作。例如将流收集成List后再返回集合的大小:

Integer collect = students.stream()
        .collect(Collectors.collectingAndThen(Collectors.toList(), List::size));


四、并行



数据并行化是将原有的数据集合分割成多个部分,然后给每个部分分配单独的处理单元。比如使用多核CPU的计算机,并行处理就会把数据分发到这多个CPU上单独处理。它与并发不同,并发是在单核上根据时间调度来处理的。当然并行计算并不意味着代码性能就一定能得到提升,影响并行计算的几个主要因素有:

  • 数据大小,数据量必须足够大,这样并行任务的拆分和结果的合并这些额外的开销可以忽略。
  • 源数据结构,通常是集合,分割相对容易。
  • 装箱,处理基本类型比处理装箱类型要快。
  • 核的数量,计算机只有一个内核,没必要并行。
  • 单元处理开销,流中每个元素的操作时间越长,并行的性能提升越明显。

流的并行化操作非常简单,有两种方式可以生成并行流:

Stream<Student> stream = students.parallelStream();
Stream<Student> stream = students.stream().parallel();


1. fork/join


并行流的底层操作使用了 fork/join 框架,JDK自带的 ForkJoinPool 线程池就可以实现这项操作,它的原理很简单,将现有的任务以递归的形式一层一层拆分成多个子任务,然后多个子任务并行操作,执行完成后将子任务的结果再合并成最终结果。操作示意图如下:微信图片11.jpg

目录
相关文章
|
17小时前
|
Java
Java中的Lambda表达式与函数式编程
【6月更文挑战第29天】在Java 8中引入的Lambda表达式,为Java语言带来了革命性的变化。它不仅简化了代码,还使得函数式编程范式在Java中的应用成为可能。本文将深入探讨Lambda表达式的概念、语法及其在Java中的应用,同时展示如何通过Lambda表达式实现函数式编程的核心概念,如不可变性、纯函数和高阶函数等。
|
1天前
|
Java 开发者
Java中的Lambda表达式与函数式接口
【6月更文挑战第28天】在现代的Java编程实践中,Lambda表达式和函数式接口已经成为提升代码简洁性和可读性的重要工具。本文将深入探讨Lambda表达式的基本概念、语法结构以及如何与函数式接口结合使用,旨在帮助开发者更好地理解和运用这一特性,以编写出更加优雅和高效的Java代码。
|
2天前
|
Java API 数据处理
Java中的lambda表达式与Stream API:高效的函数式编程
Java中的lambda表达式与Stream API:高效的函数式编程
|
2天前
|
Java API 开发者
高效利用Java中的函数式编程特性
高效利用Java中的函数式编程特性
|
3天前
|
安全 Java 大数据
深入浅出Java的函数式编程
深入浅出Java的函数式编程
|
4天前
|
Java API 开发者
探索Java中的Lambda表达式和函数式接口
【6月更文挑战第25天】在Java的世界里,Lambda表达式的引入标志着一种全新的编程范式——函数式编程。本文将通过深入解析Lambda表达式及其与函数式接口的结合使用,带领读者领略这一特性如何简化代码,提升开发效率。
|
4天前
|
安全 Java API
Java中的函数式编程:简化代码,提升效率
【6月更文挑战第25天】本文将深入探讨Java中函数式编程的概念、原理及其应用。我们将通过具体示例和代码片段来展示如何利用Java 8及以上版本中的Lambda表达式、Stream API等特性来简化代码编写,提高开发效率和程序性能。文章旨在为Java开发者提供一种现代的、更加简洁高效的编程方式。
20 5
|
5天前
|
Java 机器人 程序员
Java中的函数式编程入门
Java中的函数式编程入门
|
5天前
|
Java 机器人 程序员
Java中的lambda表达式与Stream API:高效的函数式编程
Java中的lambda表达式与Stream API:高效的函数式编程
|
5天前
|
Java 机器人 程序员
Java中的函数式编程入门指南
Java中的函数式编程入门指南