Java函数式编程(四):在集合中查找元素_java

查找元素

现在我们对这个设计优雅的转化集合的方法已经不陌生了,但它对查找元素却也是无能为力。不过filter方法却是为这个而生的。

我们现在要从一个名字列表中,取出那些以N开头的名字。当然可能一个也没有,结果可能是个空集合。我们先用老方法实现一把。

复制代码 代码如下:

final List<String> startsWithN = new ArrayList<String>();
for(String name : friends) {
if(name.startsWith("N")) {
startsWithN.add(name);
}
}

这么简单的事件,写了这么多代码,也够啰嗦的了。我们先创建了一个变量,然后把它初始为一个空集合。然后遍历原来的集合,查找那些以指定字母开头的名字。如果找到,就插入到集合里。

我们用filter方法来重构一下上面这段代码,看看它的威力到底如何。

复制代码 代码如下:

final List<String> startsWithN =
friends.stream()
.filter(name -> name.startsWith("N"))
.collect(Collectors.toList());

filter方法接收一个返回布尔值的lambda表达式。如果表达式结果为true,运行上下文中的那个元素就会被添加到结果集中;如果不是,就跳过它。最终返回的是一个Steam,它里面只包含那些表达式返回true的元素。最后我们用一个collect方法把这个集合转化成一个列表——在后面52页的使用collect方法和Collecters类中,我们会对这个方法进去更深入的探讨。

我们来打印一下这个结果集中的元素:

复制代码 代码如下:

System.out.println(String.format("Found %d names", startsWithN.size()));

从输出结果很明显能看出来,这个方法把集合中匹配的元素全都找出来了。

复制代码 代码如下:

Found 2 names

filter方法和map方法一样,也返回了一个迭代器,不过它们也就这点相同而已了。map返回的集合和输入集合大小是一样的,而filter返回的可不好说。它返回的集合的大小区间,从0一直到输入集的元素个数。和map不一样的是,filter返回的是输入集的一个子集。

到现在为止,lambda表达式带来的代码简洁性让我们很满意,不过如果不注意的话,代码冗余的问题就开始慢慢滋长了。下面我们来讨论下这个问题。

lambda表达式的重用

lambda表达式看起来很简洁,实际上一不小心很容易就出现代码冗余了。冗余会导致代码质量低下,难以维护;如果我们想做一个改动,得把好几处相关的代码都一起改掉才行。

避免冗余还可以帮忙我们提升性能。相关的代码都集中在一个地方,这样我们分析它的性能表现,然后优化这一处的代码,很容易就能提升代码的性能。

现在我们来看下为什么使用lambda表达式容易导致代码冗余,同时考虑如何去避免它。

复制代码 代码如下:

final List<String> friends =
Arrays.asList("Brian", "Nate", "Neal", "Raju", "Sara", "Scott");
final List<String> editors =
Arrays.asList("Brian", "Jackie", "John", "Mike");
final List<String> comrades =
Arrays.asList("Kate", "Ken", "Nick", "Paula", "Zach");
We want to filter out names that start with a certain letter.

我们希望过滤一下某个字母开头的名字。先用filter方法简单地实现一下。

复制代码 代码如下:

final long countFriendsStartN =
friends.stream()
.filter(name -> name.startsWith("N")).count();
final long countEditorsStartN =
editors.stream()
.filter(name -> name.startsWith("N")).count();
final long countComradesStartN =
comrades.stream()
.filter(name -> name.startsWith("N")).count();

lambda表达式让代码看起来很简洁,不过它不知不觉的带来了代码的冗余。在上面这个例子中,如果想改一下lambda表达式,我们得改不止一处地方——这可不行。幸运的是,我们可以把lambda表达式赋值给变量,然后对它们进行重用,就像使用对象一样。

filter方法,lambda表达式的接收方,接收的是一个java.util.function.Predicate函数式接口的引用。在这里,Java编译器又派上用场了,它用指定的lambda表达式生成了Predicate的test方法的一个实现。现在我们可以更明确的让Java编译器去生成这个方法,而不是在参数定义的地方再生成。在上面例子中,我们可以明确的把lambda表达式存储到一个Predicate类型的引用里面,然后再把这个引用传递给filter方法;这样做很容易就避免了代码冗余。

我们来重构前面这段代码,让它符合DRY的原则吧。(Don't Repeat Yoursef——DRY——原则,可以参看The Pragmatic Programmer: From Journeyman to Master[HT00],一书)。

复制代码 代码如下:

final Predicate<String> startsWithN = name -> name.startsWith("N");
final long countFriendsStartN =
friends.stream()
.filter(startsWithN)
.count();
final long countEditorsStartN =
editors.stream()
.filter(startsWithN)
.count();
final long countComradesStartN =
comrades.stream()
.filter(startsWithN)
.count();

现在不用再重复写那个lambda表达式了,我们只写了一次,并把它存储到了一个叫startsWithN的Predicate类型的引用里面。这后面的三个filter调用里,Java编译器看到在Predicate伪装下的lambda表达式,笑而不语,默默接收了。

这个新引入的变量为我们消除了代码冗余。不过不幸的是,后面我们就会看到,敌人很快又回来报仇雪恨了,我们再看看有什么更厉害的武器能替我们消灭它们。

时间: 2024-09-23 15:02:30

Java函数式编程(四):在集合中查找元素_java的相关文章

Java函数式编程(二):集合的使用_java

第二章:集合的使用 我们经常会用到各种集合,数字的,字符串的还有对象的.它们无处不在,哪怕操作集合的代码要能稍微优化一点,都能让代码清晰很多.在这章中,我们探索下如何使用lambda表达式来操作集合.我们用它来遍历集合,把集合转化成新的集合,从集合中删除元素,把集合进行合并. 遍历列表 遍历列表是最基本的一个集合操作,这么多年来,它的操作也发生了一些变化.我们使用一个遍历名字的小例子,从最古老的版本介绍到现在最优雅的版本. 用下面的代码我们很容易创建一个不可变的名字的列表: 复制代码 代码如下:

Java函数式编程(十二):监控文件修改_java

使用flatMap列出子目录 前面已经看到如何列出指定目录下的文件了.我们再来看下如何遍历指定目录的直接子目录(深度为1),先实现一个简单的版本,然后再用更方便的flatMap()方法来实现. 我们先用传统的for循环来遍历一个指定的目录.如果子目录中有文件,就添加到列表里:否则就把子目录添加到列表里.最后,打印出所有文件的总数.代码在下面--这个是困难模式的. 复制代码 代码如下: public static void listTheHardWay() {      List<File> f

Java函数式编程(一):你好,Lambda表达式_java

第一章 你好,lambda表达式! 第一节 Java的编码风格正面临着翻天覆地的变化. 我们每天的工作将会变成更简单方便,更富表现力.Java这种新的编程方式早在数十年前就已经出现在别的编程语言里面了.这些新特性引入Java后,我们可以写出更简洁,优雅,表达性更强,错误更少的代码.我们可以用更少的代码来实现各种策略和设计模式. 在本书中我们将通过日常编程中的一些例子来探索函数式风格的编程.在使用这种全新的优雅的方式进行设计编码之前,我们先来看下它到底好在哪里. 改变了你的思考方式 命令式风格--

Java函数式编程(八):字符串及方法引用_java

第三章 字符串,比较器和过滤器 JDK引入的一些方法对写出函数式风格的代码很有帮助.JDK库里的一些的类和接口我们已经用得非常熟悉了,比如说String,为了摆脱以前习惯的那种老的风格,我们得主动寻找机会来使用这些新的方法.同样,当我们需要用到只有一个方法的匿名内部类时,我们现在可以用lambda表达式来替换它了,不用再像原来那样写的那么繁琐了. 本章我们会使用lambda表达式和方法引用来遍历字符串,实现Comparator接口,查看目录中的文件,监视文件及目录的变更.上一章中介绍的一些方法还

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

列表的转化 将集合转化成一个新的集合就和遍历它一样简单.假设我们要将列表中的名字转化成全大写的.我们看下都有哪些实现方式. Java中的字符串是不可变的,所以它没法改变.我们可以生成新的字符串,用来替换列表中原有的元素.然而这样做的话,原来列表就没了;还有一个问题,原来的列表可能也是不可变的,比如Arrays.asList()生成的,所以修改原来的列表这招不行.还有一个缺点就是这样做很难并行操作. 生成一个新的全大写的列表是个不错的选择. 乍听起来这个建议弱爆了;性能是我们都很关注的一个问题.令

java怎样用常量接收集合中的信息?

问题描述 java怎样用常量接收集合中的信息? java怎样用常量接收集合中的信息?需求是:用for循环将座位牌号以"1-1"的形式输出,将这些信息放到集合中,选择座位牌号的时候判断有没有这个座位<<< 解决方案 首先向list中放值 /*** 将座位号放入ArrayList中* @param row 座位的行数* @param colu 座位的列数* @return 设置好数据的List */ public List setList(int row int colu

函数式编程在Redux/React中的应用

本文简述了软件复杂度问题及应对策略:抽象和组合;展示了抽象和组合在函数式编程中的应用;并展示了Redux/React在解决前端状态管理的复杂度方面对上述理论的实践.这其中包括了一段有趣的Redux推导. 软件复杂度及其应对策略 软件复杂度 软件的首要技术使命是管理复杂度.--代码大全 在软件开发过程中,随着需求的变化和系统规模的增大,我们的项目不可避免地会趋于复杂.如何对软件复杂度及其增长速率进行有效控制,便成为一个日益突出的问题.下面介绍两种控制复杂度的有效策略. 对应策略 抽象 世界的复杂.

Java函数式编程(五):闭包_java

使用词法作用域和闭包 很多开发人员都存在这种误解,认为使用lambda表达式会导致代码冗余,降低代码质量.恰恰相反,就算代码变得再复杂,我们也不会为了代码的简洁性而在代码质量上做任何妥协,下面我们就会看到. 在前面一个例子中我们已经可以重用lambda表达式了;然而,如果再匹配另外一个字母,代码冗余的问题很快又卷土重来了.我们先来进一步分析下这个问题,然后再用词法作用域和闭包来把它解决掉. lambda表达式带来的冗余 我们来从friends中过滤出那些以N或者B开头的字母.继续延用上面的那个例

Android编程实现从字符串中查找电话号码的方法_Android

本文实例讲述了Android编程实现从字符串中查找电话号码的方法.分享给大家供大家参考,具体如下: /** * 从字符串中查找数字字符串 */ private List<String> getNumbers(String content) { List<String> digitList = new ArrayList<String>(); Pattern p = Pattern.compile("(\\d+)"); Matcher m = p.ma