当前位置: 首页 > 编程笔记 >

Java函数式编程(三):列表的转化

曹振
2023-03-14
本文向大家介绍Java函数式编程(三):列表的转化,包括了Java函数式编程(三):列表的转化的使用技巧和注意事项,需要的朋友参考一下

列表的转化

将集合转化成一个新的集合就和遍历它一样简单。假设我们要将列表中的名字转化成全大写的。我们看下都有哪些实现方式。

Java中的字符串是不可变的,所以它没法改变。我们可以生成新的字符串,用来替换列表中原有的元素。然而这样做的话,原来列表就没了;还有一个问题,原来的列表可能也是不可变的,比如Arrays.asList()生成的,所以修改原来的列表这招不行。还有一个缺点就是这样做很难并行操作。

生成一个新的全大写的列表是个不错的选择。

乍听起来这个建议弱爆了;性能是我们都很关注的一个问题。令人吃惊的是,函数式编程通常要比命令式的性能要高,我们在153页的性能问题中会讲到。

我们先开始用这个集合生成一个大写字母的新集合吧。


final List<String> uppercaseNames = new ArrayList<String>();

for(String name : friends) {

uppercaseNames.add(name.toUpperCase());

}


在命令式的代码中,我们先创建一个空列表,然后把大写的名字填充进去,在遍历原来列表的过程中,每次插入一个。为了改进成函数式的版本,我们第一步可以考虑采用19页遍历列表中提到的那个内部迭代器forEach来替换一下for循环,正如下例所示的那样。

final List<String> uppercaseNames = new ArrayList<String>();

friends.forEach(name -> uppercaseNames.add(name.toUpperCase()));

System.out.println(uppercaseNames);


我们用了内部迭代器,但还得新建一个列表,然后再把元素插入到里面。我们还可以进一步改进。

使用lambda表达式

一个新引入的Stream接口里面,有个map方法,它可以帮助我们远离可变性,并使代码看起来更简洁。Steam有点像集合的迭代器,同时它还提供了流函数(fluent functions)的功能。使用这个接口的方法,我们可以把一系列调用给组合起来,使代码读起来就像描述问题的顺序一样,可读性更强。

Steam的map方法可以用来将输入序列转化成一个输出的序列——这和我们要做的工作非常匹配。


friends.stream()

.map(name -> name.toUpperCase())

.forEach(name -> System.out.print(name + " "));

System.out.println();

JDK8中的所有集合都支持这个stream方法,它把集合封装成一个Steam实例。map方法对Stream中的每个元素都调用了指定的lambda表达式或者代码块。map方法跟forEach方法很不一样, forEach只是简单的对集合中的元素执行了一下指定的函数。而map方法把lambda表达式的运行结果收齐起来,返回一个结果集。最后我们用forEach方法打印了所有的元素。

新集合中的名字全都是大写的了:


BRIAN NATE NEAL RAJU SARA SCOTT

map方法很适合把一个输入集合转化成一个新的输出集合。这个方法确保了输入输出序列的元素的数量是相同的。然而输入元素和输出元素的类型可以是不一样的。在这个例子中,我们输入和输出的都是字符串的集合。我们可以传给map方法一段代码,让它返回比如说名字中包含字符的个数。这样的话,输入的还是字符串的序列,而输出的却是数字序列了,就像下面这样。


friends.stream()

.map(name -> name.length())

.forEach(count -> System.out.print(count + " "));


结果是每个名字中字母的个数:

5 4 4 4 4 5

使用了lambda表达式的之后版本,避免了显式的修改操作;这样的代码非常简洁。这样写不再需要初始化空的集合以及那个垃圾变量了;这个变量乖乖的躲到了底层实现里面了。

使用方法引用

我们还可以使用方法引用让它变得更简洁一些。在需要传入函数式接口的实现的地方,Java编译器可以接受lambda表达式或者是方法引用。有了这个特性,用String::toUpperCase就可以替换掉name -> name.toUpperCase()了,就像这样:


friends.stream()

.map(String::toUpperCase)

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

当参数传入到这个生成的方法——函数式接口的抽象方法的实现——里面的时候,Java会去调用这个String参数的toUpperCase方法。这个参数引用在这里就隐藏起来了。像前面这种简单的场景,我们可以用方法引用来替换掉lambda表达式;更多的内容看一下26页的什么时候应该使用方法引用。


小伙伴发问了: 

什么时候应该使用方法引用?

当使用Java编程的时候,通常我们用lambda表达式的时候要比方法引用多得多。但这并不意味着方法引用不重要或者没啥用处。当lambda表达式非常简短的时候,它是一个很好的替代方案,它直接调用了实例方法或者静态方法。也就是说,如果lambda表达式只是传递了一下参数给方法调用的话,我们应该改用方法引用。 像这样的lambda表达式,有点像Tom Smykowski在电影上班一条虫中讲的那样,它的工作就是"从客户那把需求拿给软件工程师"。因为这个,我把这种重构成方法引用的模式叫做上班一条虫模式。 除了简洁外,使用方法引用,方法名字本身的含义和作用可以更好的体现出来。 使用方法引用背后,编译器起到了很关键的作用。方法引用的目标对象和参数都会从这个生成的方法里传进来的参数那推导出来。这才使得你可以使用方法引用写出比使用lambda表达式更简洁的代码。不过,如果参数在传递给方法之前或者调用结果在返回之后要被修改的话,这种便利的写法我们就用不了了。

在前面这个例子中,方法引用是引用了一个实例方法。方法引用还可以引用一个静态方法以及接受传参的方法。后面我们会看到这样的例子。

lambda表达式能帮助我们遍历集合,并且进行集合的转化。就像下面我们即将看到的,它还能帮助我们快速的从集合中选取一个元素。

 类似资料:
  • 问题内容: 如何在Java中模拟函数式编程,特别是如何将函数映射到项目集合? 什么是最冗长和尴尬的方法? 问题答案: 在Java之前,所有的函数式编程尝试在Java中都会有些 冗长 和/或 笨拙 ,直到Java 8。 最 直接的 方法是提供一个接口(例如Guava的这种形式),并提供采用和调用该接口的各种方法(例如我认为您的方法应该执行的操作)。 不好的事情是,您需要使用匿名内部类来实现并经常这样

  • 本文向大家介绍Java函数式编程(一):你好,Lambda表达式,包括了Java函数式编程(一):你好,Lambda表达式的使用技巧和注意事项,需要的朋友参考一下 第一章 你好,lambda表达式! 第一节 Java的编码风格正面临着翻天覆地的变化。 我们每天的工作将会变成更简单方便,更富表现力。Java这种新的编程方式早在数十年前就已经出现在别的编程语言里面了。这些新特性引入Java后,我们可以

  • 本文向大家介绍详解JAVA 函数式编程,包括了详解JAVA 函数式编程的使用技巧和注意事项,需要的朋友参考一下 1.函数式接口 1.1概念: java中有且只有一个抽象方法的接口。 1.2格式: 1.3@FunctionalInterface注解: 与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注 解可用于

  • 本文向大家介绍Java函数式编程(六):Optional,包括了Java函数式编程(六):Optional的使用技巧和注意事项,需要的朋友参考一下 选取单个元素 直觉来说选取单个元素肯定会比选取多个要简单得多,不过这里也存在一些问题。我们先看下一般的做法的问题是什么,然后再看下如何用lambda表达式来解决它。 我们先新建一个方法来查找一个以特定字母开头的元素,然后打印出来。 这个方法简直跟刚过去

  • 本文向大家介绍Java函数式编程(七):MapReduce,包括了Java函数式编程(七):MapReduce的使用技巧和注意事项,需要的朋友参考一下 译注:map(映射)和reduce(归约,化简)是数学上两个很基础的概念,它们很早就出现在各类的函数编程语言里了,直到2003年Google将其发扬光大,运用到分布式系统中进行并行计算后,这个组合的名字才开始在计算机界大放异彩(那些函数式粉可能并不

  • 本文向大家介绍Java函数式编程(九):Comparator,包括了Java函数式编程(九):Comparator的使用技巧和注意事项,需要的朋友参考一下 实现Comparator接口 Comparator接口的身影在JDK库中随处可见,从查找到排序,再到反转操作,等等。Java 8里它变成了一个函数式接口,这样的好处就是我们可以使用流式语法来实现比较器了。 我们用几种不同的方式来实现一下Comp