本系列文章译自Venkat Subramaniam的
Functional Programming in Java
第二章:集合的使用
我们经常会用到各种集合,数字的,字符串的还有对象的。它们无处不在,哪怕操作集合的代码要能稍微优化一点,都能让代码清晰很多。在这章中,我们探索下如何使用lambda表达式来操作集合。我们用它来遍历集合,把集合转化成新的集合,从集合中删除元素,把集合进行合并。
遍历列表
遍历列表是最基本的一个集合操作,这么多年来,它的操作也发生了一些变化。我们使用一个遍历名字的小例子,从最古老的版本介绍到现在最优雅的版本。
用下面的代码我们很容易创建一个不可变的名字的列表:
final List<String> 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<还是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<String>() { 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表达式调用的活。它可以决定延迟表达式的执行,如果合适的话还可以进行并行计算。
这个版本的输出也和前面的一样。
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函数式编程是一种编程范式,它强调使用函数作为程序的基本构建块,将计算视为函数的组合,并且尽可能避免改变状态和可变数据。在Java 8及更高版本中,函数式编程得到了官方的大力支持,引入了Lambda表达式、...
Java函数式编程是一种将函数作为一等公民的编程范式,它强调使用函数来构造程序,减少副作用,提高代码的可读性和可维护性。在Java 8及更高版本中,函数式编程得到了显著增强,引入了Lambda表达式、函数接口、Stream...
Java函数式编程是一种高效、简洁的编程范式,它在Java 8中得到了全面支持,大大改变了Java开发人员编写代码的方式。本套黑马程序员的Java函数式编程视频教程涵盖了Lambda表达式、Stream流以及函数式编程的核心概念,...
Java函数式编程是一种编程范式,它将计算视为数据处理,并强调程序的数据流和变换,而不是对指令进行控制。在Java 8及更高版本中,函数式编程得到了强大的支持,引入了Lambda表达式、Stream API以及函数式接口等概念...
函数式编程及Java集合.pptx(培训)
Java 8是一个重要的Java语言版本,它引入了对函数式编程的支持,极大地提升了代码的简洁性和可读性,特别是对于集合操作。函数式编程是一种编程范式,它将计算视为函数的组合,而不是状态的改变或控制流程。在Java 8...
在Java 8中,许多集合API如`Stream`、`Optional`等都进行了函数式编程的增强。通过Lambda表达式,我们可以方便地对集合进行过滤、映射、归约等操作。例如,以下代码展示了如何使用Lambda表达式过滤出一个整数列表中...
面向Java开发者的函数式编程是一种将函数式编程思想应用于Java开发中的实践方法。函数式编程是一种编程范式,强调程序数据的不可变性、避免副作用,并利用纯函数来构造软件。这种编程风格在处理复杂性、提高代码...
### 深入理解Java函数式编程和Streams API #### 一、引言 随着Java 8的发布,函数式编程范式正式被引入到Java语言中,这标志着Java编程方式的重大转变。Java 8引入了Lambda表达式、方法引用等新特性,使得Java能够...
Java 8是Java语言发展史上一个重要的里程碑,它引入了多种新特性,旨在增强Java语言表达...随着这些新特性的广泛使用,Java社区看到了编程方式的重大转变,从传统的命令式编程向更加现代、声明式的函数式编程方式迁移。
Java函数式编程是指利用函数式编程的思想和特性来开发Java应用程序。函数式编程强调将计算过程视为数学函数的求值,侧重于函数的组合、映射、过滤等操作,同时避免了可变状态和可变数据。 在Java中,函数式编程主要...
Java 8 是一个重要的Java版本,它引入了函数式编程的概念,极大地扩展了语言的功能,使得Java开发者可以采用更加简洁、高效的方式来编写代码。Richard Warburton 的《java 8 函数式编程》是一本深入解析这一主题的...
Java 8是Java语言的一个重要版本,引入了大量新特性,其中最具革命性的就是函数式编程的支持。函数式编程在Java 8中的实现主要体现在Lambda表达式、Stream API以及方法引用等方面,这些新特性极大地提高了代码的简洁...
Java函数式编程是一种编程范式,它将计算视为函数的评估,强调数据的无副作用处理和函数的纯粹性。在Java 8及更高版本中,函数式编程得到了大力支持,引入了Lambda表达式、函数接口和Stream API等核心特性,极大地...
【Java 8 函数式编程】是针对Java开发者的一本技术书籍,旨在介绍Java 8引入的函数式编程概念和特性。作者Richard Warburton希望通过这本书挑战传统观念,即函数式编程只适合少数专业人士,而让更多的程序员能够理解...
Java 8的Stream API提供了函数式编程的基础设施,允许开发者以声明式方式处理集合数据。`map()`、`filter()`、`reduce()`等方法使得对数据的操作变得简洁且易于理解。 **并行处理** Stream API支持并行流,这使得...
Java函数式编程是一种编程范式,它强调使用函数作为一等公民,允许将函数作为参数传递,也可以作为返回值。这种编程风格在处理大量数据时特别有用,因为它提供了简洁、可读性强的代码,同时也提高了代码的并行执行...
【Java 8函数式编程】 Java 8 是Java平台的重大更新,它引入了一种新的编程范式——函数式编程,这使得Java开发者能够利用更简洁、高效的方式编写代码。函数式编程强调使用纯函数,避免副作用,以及数据的不可变性...
Java函数式编程是一种编程范式,它源自数学概念,强调使用函数作为主要构造块,并避免可变状态和副作用。在Java中,从Java 8开始引入了对函数式编程的支持,特别是通过Lambda表达式和流API。让我们深入了解这个概念...
总之,Java 8的函数式编程特性极大地提升了代码的简洁性和可读性,使得处理序列和集合数据变得更加方便。对于开发者来说,理解并熟练运用Lambda表达式和Stream API是提升编程能力的重要一步。通过实践,如生成字母...