第二章:集合的使用

我们经常会用到各种集合,数字的,字符串的还有对象的。它们无处不在,哪怕操作集合的代码要能稍微优化一点,都能让代码清晰很多。在这章中,我们探索下如何使用lambda表达式来操作集合。我们用它来遍历集合,把集合转化成新的集合,从集合中删除元素,把集合进行合并。

遍历列表

遍历列表是最基本的一个集合操作,这么多年来,它的操作也发生了一些变化。我们使用一个遍历名字的小例子,从最古老的版本介绍到现在最优雅的版本。

用下面的代码我们很容易创建一个不可变的名字的列表:

final List friends =

Arrays.asList("Brian", "Nate", "Neal", "Raju", "Sara", "Scott");

System.out.println(friends.get(i));

}

下面这是最常见的一种遍历列表并打印的方法,虽然也最一般:

for(int i = 0; i < friends.size(); i++) {

System.out.println(friends.get(i));

}

我把这种方式叫做自虐型写法——又啰嗦又容易出错。我们得停下来好好想想,"是i

Java还提供了一种相对先进的for结构。

collections/fpij/Iteration.java

for(String name : friends) {

System.out.println(name);

}

在底层,这种方式的迭代是使用Iterator接口来实现的,调用了它的hasNext和next方法。

这两种方式都属于外部迭代器,它们把如何做和想做什么揉到了一起。我们显式的控制迭代,告诉它从哪开始到哪结束;第二个版本则在底层通过Iterator的方法来做这些。显式的操作下,还可以用break和continue语句来控制迭代。

第二个版本比第一个少了点东西。如果我们不打算修改集合的某个元素的话,它的方式比第一个要好。不过这两种方式都是命令式的,在现在的Java中应该摒弃这种方式。

改成函数式原因有这几个:

for循环本身是串行的,很难进行并行化。

这样的循环是非多态的;所得即所求。我们直接把集合传给for循环,而不是在集合上调用一个方法(支持多态)来执行特定的操作。

从设计层面来说,这样 写的代码违反了“Tell,Don't Ask”的原则 。我们请求执行一次迭代,而不是把迭代留给底层库来执行。

是时候从老的命令式编程转换到更优雅的内部迭代器的函数式编程了。使用内部迭代器后我们把很多具体操作都扔给了底层方法库来执行,你可以更专注于具体的业务需求。底层的函数会负责进行迭代的。我们先用一个内部迭代器来枚举一下名字列表。

Iterable接口在JDK8中得到加强,它有一个专门的名字叫forEach,它接收一个Comsumer类型的参数。如名字所说,Consumer的实例正是通过它的accept方法消费传递给它的对象的。我们用一个很熟悉的匿名内部类的语法来使用下这个forEach方法:

friends.forEach(new Consumer() { public void accept(final String name) {

System.out.println(name); }

});

我们调用了friends集合上的forEach方法,给它传递了一个Consumer的匿名实现。这个forEach方法从对集合中的每一个元素调用传入的Consumer的accept方法,让它来处理这个元素。在这个示例中我们只是打印了一下它的值,也就是这个名字。

我们来看下这个版本的输出结果,和上两个的结果 是一样的:

Brian

Nate

Neal

Raju

Sara

Scott

我们只改了一个地方:我们抛弃了过时的 for循环,使用了新的内部迭代器。好处是,我们不用指定如何迭代这个集合,可以更专注于如何处理每一个元素。缺点是,代码看起来更啰嗦了——这简直要把新的编码风格带来的喜悦冲的一干二净了。所幸的是,这个很容易改掉,这正是lambda表达式和新的编译器的威力大展身手的时候了。我们再做一点修改,把匿名内部类换成lambda表达式。

friends.forEach((final String name) -> System.out.println(name));

这样看起来就好多了。代码更少了,不过我们先来看下这是什么意思。这个forEach方法是一个高阶函数,它接收一个lambda表达式或者代码块,来对列表中的元素进行操作。在每次调用的时候 ,集合中的元素会绑定到name这个变量上。底层库托管了lambda表达式调用的活。它可以决定延迟表达式的执行,如果合适的话还可以进行并行计算。

这个版本的输出也和前面的一样。

{% highlight java%}

Brian

Nate

Neal

Raju

Sara

Scott

内部迭代器的版本更为简洁。而且,使用它的话我们可以更专注每个元素的处理操作,而不是怎么去遍历——这可是声明式的。

不过这个版本还有缺陷。一旦forEach方法开始执行了,不像别的两个版本,我们没法跳出这个迭代。(当然有别的方法能搞定这个)。因此,这种写法在需要对集合里的每个元素处理的时候比较常用。后面我们会介绍到一些别的函数可以让我们控制循环的过程。

lambda表达式的标准语法,是把参数放到()里面,提供类型信息并使用逗号分隔参数。Java编译器为了解放我们,还能自动进行类型推导。不写类型当然更方便了,工作少了,世界也清静了。下面是上一个版本去掉了参数类型之后的:

friends.forEach((name) -> System.out.println(name));

在这个例子里,Java编译器通过上下文分析,知道name的类型是String。它查看被调用方法forEach的签名,然后分析参数里的这个函数式接口。接着它会分析这个接口里的抽象方法,查看参数的个数及类型。即便这个lambda表达式接收多个参数,我们也一样能进行类型推导,不过这样的话所有参数都不能带参数类型;在lambda表达式中,参数类型要么全不写,要写的话就得全写。

Java编译器对单个参数的lambda表达式会进行特殊处理:如果你想进行类型推导的话,参数两边的括号可以省略掉。

friends.forEach(name -> System.out.println(name));

这里有一点小警告:进行类型推导的参数不是final类型的。在前面显式声明类型例子中,我们同时也把参数标记为final的。这样能防止你在lambda表达式中修改参数的值。通常来说,修改参数的值是个坏习惯,这样容易引起BUG,因此标记成final是个好习惯。不幸的是,如果我们想使用类型推导的话,我们就得自己遵守规则不要修改参数,因为编译器可不再为我们保驾护航了。

走到这步可费了老劲了,现在代码量确实少了一点。不过这还不算最简。我们来体验下最后这个极简版的。

friends.forEach(System.out::println);

在上面的代码中我们用到了一个方法引用。我们用方法名就可以直接替换整个的代码了。在下节中我们会深入探讨下这个,不过现在我们先来回忆下Antoine de Saint-Exupéry的一句名言:完美不是无法再增添加什么,而是无法再去掉什么。

lambda表达式让我们能够简洁明了的进行集合的遍历。下一节我们会讲到它如何使我们在进行删除操作和集合转化的时候,也能够写出如此简洁的代码。

未完待续,后续文章请继续关注deepinmind。

原创文章转载请注明出处:http://it.deepinmind.com

Java for函数用法_Java函数式编程(四)集合的使用相关推荐

  1. random函数用法_Python函数式编程:从入门到走火入魔

    很多人都在谈论函数式编程(Functional Programming),只是很多人站在不同的角度看到的是完全不一样的风景.坚持实用主义的 Python 老司机们对待 FP 的态度应该更加包容,虽然他 ...

  2. java random函数用法_JAVA的Random类的用法详解

    Random类主要用来生成随机数,本文详解介绍了Random类的用法,希望能帮到大家. Random类 (java.util) Random类中实现的随机算法是伪随机,也就是有规则的随机.在进行随机时 ...

  3. java函数式编程例子_java函数式编程Lambda表达式的示例(一)

    函数式编程是时下比较流行的编程方式了,很多新兴的编程语言都对函数式编程有了比较好的支持,她有别于传统的命令式编程,可以将函数(执行代码的过程)作为参数进行传递.JAVA也意识到了函数式编程的重要性,在 ...

  4. Java 8 Lambda表达式的函数式编程– Monads

    什么是monad ?: monad是一种设计模式概念,用于大多数功能编程语言(如Lisp)或现代世界的Clojure或Scala中. (实际上,我会从scala复制一些内容.)现在,为什么它在Java ...

  5. c语言count函数的用法,java count函数用法

    java count函数用法 [2021-02-11 06:55:28]  简介: 在excel中count函数的功能是:计算数字的个数,对给定数据集合或者单元格区域中数据的个数进行计数.COUNT函 ...

  6. java convert函数_Java 函数式编程和Lambda表达式

    1.Java 8最重要的新特性 Lambda表达式.接口改进(默认方法)和批数据处理. 2.函数式编程 本质上来说,编程关注两个维度:数据和数据上的操作. 面向对象的编程泛型强调让操作围绕数据,这样可 ...

  7. predicate java 作用_Java函数式编程接口详解之Predicate

    一.初识 Predicate是Java提供的重要的函数编程接口之一,作用主要是用于逻辑判断. 首先看看源码: @FunctionalInterface public interface Predica ...

  8. java函数式编程_Java 函数式编程和 lambda 表达式详解

    作者:DemonsI my.oschina.net/demons99/blog/2223079 为什么要使用函数式编程 函数式编程更多时候是一种编程的思维方式,是种方法论.函数式与命令式编程的区别主要 ...

  9. java 柯里化_函数式编程(Java描述)——Java中的函数及其柯里化

    本文继续上一篇的内容 在Java中,函数可以表现为一个普通的方法.一个lambda表达式,又或者方法引用,甚至是匿名类.本文不会介绍匿名类这种形式. 方法 Java中的方法,Java使用方法这一概念来 ...

最新文章

  1. pythonselenium浮动框_python上selenium的弹框操作实现
  2. mysql安装后第一次操作_MySQL数据库之mysql 安装成功以及第一次安装成功初始化密码操作...
  3. 3.8 注意力模型-深度学习第五课《序列模型》-Stanford吴恩达教授
  4. es查询大文本效率_es中terms查询速度能否优化
  5. html zoom中心,css中的zoom的使用
  6. 有初学的同学问你为什么Java的main方法必须是public static void?请告诉ta!
  7. 线性及非线性方程组的解法
  8. 如何使计算机为您读取文档
  9. 排序算法之快速排序(Java)
  10. [Kesci] 预测分析 · 客户购买预测(AUC评估要使用predict_proba)
  11. mysql内部实现原理面试_理解完这些基本上能解决面试中MySql的事务问题
  12. CombineFileInputFormat 文件分片总结
  13. System verilog随机系统函数$urandom使用方法
  14. 使用火狐浏览器的原因是什么?使用英文版火狐的原因又是什么?
  15. 滴滴App突发Bug:滴滴一下,千元出发
  16. Reapp - 下一代的 Hybrid App 开发框架
  17. java 多态 静态方法_java:从具有多态性的未知类调用静态方法
  18. Mpass – PHP做Socket服务的解决方案
  19. 【电力电子】【2020.02】利用导抗式三相双有源桥DC-DC变换器实现宽范围高效率的拓扑结构和调制方案
  20. JavaScript学习手册三:JS运算符

热门文章

  1. UART串口传图LCD显示----图像处理
  2. 【沃顿商学院学习笔记】领导力——Business Impact:03商业如何驱动影响力的案例Some Cases
  3. 7-1 最长公共子序列 (20 分)
  4. shell脚本编程大全
  5. 微软xcloud服务器,微软公布云游戏服务Project xCloud详情 2019年公测
  6. 【算法导论】 内部排序算法总结
  7. vue2.0中的退出登录问题
  8. 更改设置并对计算机自定义,电脑别乱玩 禁用Win8.1自定义设置项
  9. 云脉高效数字化档案管理
  10. 天地图实现标注用户当前坐标位置