`
deepinmind
  • 浏览: 451370 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
1dc14e59-7bdf-33ab-841a-02d087aed982
Java函数式编程
浏览量:41612
社区版块
存档分类
最新评论

Java函数式编程(十二)收集器

阅读更多
本系列文章译自Venkat Subramaniam的Functional Programming in Java

未完待续,后续文章请继续关注[url={{ site.url }}]Java译站[/url]。

前面我们已经用过几次collect()方法来将Stream返回的元素拼成ArrayList了。这是一个reduce操作,它对于将一个集合转化成另一种类型(通常是一个可变的集合)非常有用。collect()函数,如果和Collectors工具类里的一些方法结合起来使用的话,能提供极大的便利性,本节我们将会介绍到。

我们还是继续使用前面的Person列表作为例子,来看一下collect()方法到底有哪些能耐。假设我们要从原始列表中找出所有大于20岁的人。下面是使用了可变性和forEach()方法实现的版本:

List<Person> olderThan20 = new ArrayList<>(); people.stream()
        .filter(person -> person.getAge() > 20)
.forEach(person -> olderThan20.add(person)); System.out.println("People older than 20: " + olderThan20);


我们使用filter()方法来从列表中过滤出了所有年龄大于20的人。然后,在forEach方法里,我们将元素添加到一个在前面已经初始化好的ArrayList中。我们先看下这段代码的输出结果,一会儿再去重构它。

People older than 20: [Sara - 21, Jane - 21, Greg - 35]


程序输出的结果是对的,不过还有点小问题。首先,把元素添加到集合中,这种属于低级操作——它是命令式的,而非声明式的。如果我们想把这个迭代改造成并发的,还得去考虑线程安全的问题——可变性使得它难以并行化。幸运的是,使用collect()方法可以很容易解决掉这个问题。来看下如何实现的。

collect()方法接受一个Stream并将它们收集到一个结果容器中。要完成这个工作,它需要知道三个东西:
  • 如何创建结果容器(比如说,使用ArrayList::new方法)
  • 如何把单个元素添加到容器中(比如使用ArrayList::add方法)
  • 如何把一个结果集合并到另一个中(比如使用ArrayList::addAll方法)

  • 对于串行操作而言,最后一条不是必需的;代码设计的目标是能同时支持串行和并行的。

    我们把这些操作提供给collect方法,让它来把过滤后的流给收集起来。

    List<Person> olderThan20 =
    people.stream()
    .filter(person -> person.getAge() > 20)
    .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
    System.out.println("People older than 20: " + olderThan20);
    


    这段代码的结果和前面一样,不过这样写有诸多好处。

    首先,我们编程的方式更聚焦了,表述性也更强,清晰的传达了你要把结果收集到一个ArrayList里去的目的。collect()的第一个参数是一工厂或者生产者,后面的参数是一个用来收集元素的操作。

    第二,由于我们没有在代码中个执行显式的修改操作,可以很容易并行地执行这个迭代。我们让底层库来完成修改操作,它自己会处理好协作及线程安全的问题,尽管ArrayList本身不是线程安全的——干的漂亮。

    如果条件允许的话,collect()方法可以并行地将元素添加到不同的子列表中,然后再用一个线程安全的方式将它们合并到一个大列表里(最后一个参数就是用来进行合并操作的)。

    我们已经看到,相对于手动把元素添加到列表而言,使用collect()方法的好处真是太多了。下面我们来看下这个方法的一个重载的版本——它更简单也更方便——它是使用一个Collector作为参数。这个Collector是一个包含了生产者,添加器,以及合并器在内的接口——在前面的版本中这些操作是作为独立的参数分别传入方法中的——使用Collector则更简单并且可以复用。Collectors工具类提供了一个toList方法,可以生成一个Collector的实现,用来把元素添加到ArrayList中。我们来修改下前面那段代码,使用一下这个collect()方法。

    List<Person> olderThan20 =
    people.stream()
    .filter(person -> person.getAge() > 20)
    .collect(Collectors.toList());
    System.out.println("People older than 20: " + olderThan20);
    


    使用了Collectors工具类的简洁版的collect()方法,可不止这一种用法。Collectors工具类中还有好几种不同的方法来可以进行不同的收集和添加的操作。比如说,除了toList()方法,还有toSet()方法,可以添加到一个Set中,toMap()方法可以用来收集到一个key-value的集合中,还有joining()方法,可以拼接成一个字符串。我们还可以将mapping(),collectingAndThen(),minBy(), maxBy()和groupingBy()等方法组合起来进行使用。

    我们来用下groupingBy()方法来将人群按年龄进行分组。

    Map<Integer, List<Person>> peopleByAge =
    people.stream()
    .collect(Collectors.groupingBy(Person::getAge));
    System.out.println("Grouped by age: " + peopleByAge);
    


    只需简单的调用下collect()方法便能完成分组。groupingBy()接受一个lambda表达式或者方法引用——这种叫分类函数——它返回需要分组的对象的某个属性的值。根据我们这个函数返回的值,来把调用上下文中的元素放进某个分组中。在输出中可以看到分组的结果:

    Grouped by age: {35=[Greg - 35], 20=[John - 20], 21=[Sara - 21, Jane - 21]}
    


    这些人已经按年龄进行了分组。

    在前面这个例子中我们按人群的年龄对他们进行了分组收集。groupingBy()方法的一个变种可以按多个条件进行分组。简单的groupingBy()方法使用了分类器进行元素收集。而通用的groupingBy()收集器,则可以为每一个分组指定一个收集器。也就是说,元素在收集的过程中会途经不同的分类器和集合,下面我们将会看到。

    继续使用上面这个例子,这回我们不按年龄分组了,我们只获取人的名字,按他们的年龄进行排序。

    Map<Integer, List<String>> nameOfPeopleByAge =
    people.stream()
    .collect(
    groupingBy(Person::getAge, mapping(Person::getName, toList())));
    System.out.println("People grouped by age: " + nameOfPeopleByAge);
    


    这个版本的groupingBy()接受两个参数:第一个是年龄,这是分组的条件,第二个是一个收集器,它是由mapping()函数返回的结果。这些方法都来自Collectors工具类,在这段代码中进行了静态的导入。mapping()方法接受两个参数,一个是映射用的属性,一个是对象要收集到的地方,比如说list或者set。来看下上面这段代码的输出结果:

    People grouped by age: {35=[Greg], 20=[John], 21=[Sara, Jane]}
    


    可以看到,人们的名字已经按年龄进行分组了。

    我们再来看一个组合的操作:按名字的首字母进行分组,然后选出每个分组中年纪最大的那位。

    Comparator<Person> byAge = Comparator.comparing(Person::getAge);
    Map<Character, Optional<Person>> oldestPersonOfEachLetter =
    people.stream()
    .collect(groupingBy(person -> person.getName().charAt(0),
    reducing(BinaryOperator.maxBy(byAge))));
    System.out.println("Oldest person of each letter:");
    System.out.println(oldestPersonOfEachLetter);
    
    


    我们先是按名字的首字母进行了排序。为了实现这个,我们把一个lambda表达式作为groupingBy()的第一个参数传了进去。这个lambda表达式是用来返回名字的首字母的,以便进行分组。第二个参数不再是mapping()了,而是执行了一个reduce操作。在每个分组内,它使用maxBy()方法,从所有元素中递推出最年长的那位。由于组合了许多操作,这个语法看起来有点臃肿,不过整个读起来是这样的:按名字首字母进行分组,然后递推出分组中最年长的那位。来看下这段代码的输出,它列出了指定字母开头的那组名字中年纪最大的那个人。

    Oldest person of each letter:
    {S=Optional[Sara - 21], G=Optional[Greg - 35], J=Optional[Jane - 21]}
    


    我们已经领教到了collect()方法以及Collectors工具类的威力。在你的IDE或者JDK的官方文档里面,再花点时间去研究下Collectors工具类吧,熟悉下它提供的各种方法。下面我们将会用lambda表达式来完成一些过滤器的实现。


    未完待续,后续文章请继续关注[url={{ site.url }}]Java译站[/url]。

    原创文章转载请注明出处:http://it.deepinmind.com
    3
    2
    分享到:
    评论

    相关推荐

      黑马程序员Java函数式编程全套视频教程,Lambda表达式、Stream流、函数式编程一套全通关1

      Java函数式编程是一种高效、简洁的编程范式,它在Java 8中得到了全面支持,大大改变了Java开发人员编写代码的方式。本套黑马程序员的Java函数式编程视频教程涵盖了Lambda表达式、Stream流以及函数式编程的核心概念,...

      java函数式编程

      Java函数式编程是一种编程范式,它将计算视为数据处理,并强调程序的数据流和变换,而不是对指令进行控制。在Java 8及更高版本中,函数式编程得到了强大的支持,引入了Lambda表达式、Stream API以及函数式接口等概念...

      深入理解Java函数式编程和Streams API

      ### 深入理解Java函数式编程和Streams API #### 一、引言 随着Java 8的发布,函数式编程范式正式被引入到Java语言中,这标志着Java编程方式的重大转变。Java 8引入了Lambda表达式、方法引用等新特性,使得Java能够...

      深入理解Java函数式编程和Streams API.zip

      Java函数式编程是一种编程范式,它强调使用函数作为一等公民,允许将函数作为参数传递,也可以作为返回值。这种编程风格在处理大量数据时特别有用,因为它提供了简洁、可读性强的代码,同时也提高了代码的并行执行...

      java代码-java函数式编程

      Java函数式编程是Java 8引入的一项重要特性,它极大地扩展了Java语言的功能,使得开发者可以采用更加简洁、高效的方式处理问题。函数式编程的核心思想是将计算视为函数的求值,强调数据的不可变性,避免副作用,以及...

      Scala与Clojure函数式编程

      在函数式编程中,数据和函数结合得非常紧密,数据被视为函数作用的原象,而函数则是数据的变换器。函数式编程在逻辑上摒弃了可变数据结构,倾向于使用不可变的数据集合以及纯函数。这种编程范式在处理并发时,能够...

      Java 8函数式编程学习笔记.zip

      Java 8是Java语言的一个重要版本,引入了大量新特性,其中最具革命性的就是函数式编程的支持。函数式编程是一种编程范式,它强调将计算视为函数的组合,避免可变状态和副作用,使得代码更加简洁、易于测试和并行化。...

      Java函数式编程(十):收集器

      Java函数式编程中的收集器(Collector)是Stream API的一个强大特性,它允许我们将Stream操作的结果汇总到某种形式的集合中。在Java 8引入的函数式编程范式中,collect()方法是Stream API的关键组成部分,它能够将...

      672.670.JAVA基础教程_动态代理与Java8新特性-Java内置的函数式接口介绍(672).rar

      而Java 8的新特性则极大地改变了我们编写代码的方式,尤其是引入了函数式编程的概念,让Java更加简洁和高效。 首先,让我们来理解动态代理。在Java中,动态代理通常通过`java.lang.reflect.Proxy`类和`java.lang....

      The Java Language Specification Java SE 8 Edition Java编程规范

      Java 8在集合框架上添加了流(Stream)API,提供了函数式编程风格,支持并行处理,极大地提高了数据处理效率。 七、并发编程 Java提供了丰富的并发工具,如ExecutorService、Semaphore、CountDownLatch等,帮助...

      Java Version 3.5编程指南

      它还可能扩展了lambda表达式,提高了函数式编程的能力,使得代码更简洁且可读性更强。此外,新版本可能会对类型推断进行优化,使得泛型的使用更为方便。 在类库方面,Java 3.5可能会提供新的API或者对现有API进行了...

      Java 高级编程面试准备资料

      14. **Lambda表达式与函数式编程**:熟悉Java 8引入的Lambda表达式,理解函数式接口,以及Stream API的使用。 15. **模块化系统(Jigsaw)**:了解Java 9引入的模块系统,包括模块的声明、依赖和打包。 这些只是...

      Java实用教程第章Java语言及编程环境.pptx

      - Lambda表达式:引入函数式编程,简化代码,增加语言灵活性。 - 新的时间日期API:改进日期时间处理能力。 - 流API:支持管道操作,配合lambda表达式优化数据处理。 - 接口默认方法:接口可以提供默认实现,...

      Java高级编程教学幻灯片

      8. **Java 8及更高版本的新特性**:可能包含了函数式编程概念,如Lambda表达式,Stream API,Optional类,日期和时间API的改进,以及接口的默认方法和静态方法。 9. **注解(Annotation)**:注解提供了一种元数据...

      浅谈JAVA8给我带了什么——流的概念和收集器

      5. **函数式编程**:流的使用鼓励采用函数式编程风格,减少副作用,提高代码的可测试性和可维护性。 收集器(Collectors)是流操作的一个重要组成部分,它们负责将流中的元素转换为其他形式,如转换为集合、计算...

      java面向对象笔试题-awesome-functional-programming:另一个用于收集有关函数式编程的文章、视频等的资源

      一种编程语言,旨在作为编程教育的绝佳选择,同时探索脚本和函数式编程的融合 - 动态函数式语言 - 一种在 JVM 上构建可扩展系统的强大语言 - 具有效果推断功能的面向函数的语言 - 一种超级可爱的依赖类型语言! -...

      Java9尝鲜之交互式编程环境共18页.pdf.zip

      开发者可以轻松地试验语言特性,如新的模块系统或者函数式编程概念。 2. 模块系统(Project Jigsaw) Java9的模块系统是其核心改进之一,它提高了大型应用的可维护性和安全性。模块化使得代码组织更加有序,每个...

      深入探讨javascript函数式编程

      JavaScript函数式编程是一种编程范式,它强调将计算视为对数据的函数应用,而不是通过修改状态或跳转控制流程来解决问题。在这个模式下,函数被视为第一类公民,意味着它们可以作为参数传递,作为其他函数的返回值,...

      JavaNew1-8.rar_Java 8_javanew

      9. **并行收集器**:Java 8改进了垃圾收集器,特别是G1(Garbage First)收集器,支持并行和并发,提高了应用性能。 10. **接口的私有方法和静态方法**:Java 8允许在接口中定义私有方法和静态方法,增强了接口的...

      精通java核心技术编程

      13. **Lambda表达式(Java 8+)**:Lambda表达式简化了函数式编程,使代码更简洁。 14. **Stream API(Java 8+)**:Stream API提供了对集合和其他数据源的声明式处理,支持并行计算。 15. **Java虚拟机(JVM)**...

    Global site tag (gtag.js) - Google Analytics