试听热线:
  029-62258374
网站首页 JavaEE全栈工程师 WEB前端工程师 专家师资 就业案例 常见问题 视频下载 报名流程 关于我们
当前栏目
常见问题
最新文章
西安尚学堂2019年09月第
西安尚学堂2019年08月第
西安尚学堂2019年08月第
Java和Python谁更有
西安尚学堂2019年08月第
西安尚学堂2019年07月第
Java面试如何描述自己的项
西安尚学堂2019年07月第
2019年Java语言发展趋
西安尚学堂2019年07月第
热门信息
尚学堂学费是多少?
IT培训为什么选择尚学堂
尚学堂四大保障为您保驾护航
张*利,毕业于西安工业大学,
孙*,毕业于陕西能源职业技术
您当前的位置:首页>> 常见问题
为什么Java完美的lambda表达式只有一行
作者:管理员    来源:尚学堂   发布时间:2017-12-18 15:24:56  阅读:3238次


lambda.jpg


Java 8 是 Java 语言自诞生以来最重大的更新 — 它包含如此丰富的新特性,以至于您可能不知道从何处入手。在本系列中提供了一种惯用的 Java 8 编程方法:这些简短的探索会激发您反思您认为理所当然的 Java 约定,同时逐步将新技术和语法集成到您的程序中。


目前您已在本系列中了解到,函数组合的一个主要好处是它会获得富于表达的代码。编写简短的 lambda 表达式是实现这一表达能力的关键,但通常说起来容易做起来难。本文会加深您目前对创建单行 lambda 表达式的各个方面的了解。通过学习函数组合的结构和好处,您很快就会掌握完美的 lambda 表达式,—一个仅短短一行的表达式。


编写 lambda 表达式的两种方法

众所周知,lambda 表达式是匿名函数,它们天生就很简洁。普通的函数或方法通常有 4 个元素:

1、一个名称

2、返回类型

3、参数列表

4、主体

在这里可以看到,lambda 表达式只有这 4 元素中的最后两个:

(parameter list) -> body

“->” 将参数列表与函数主体分离,旨在对给定参数进行处理。函数的主体可能是一个表达式或一条语句。下面给出了一个示例:

(Integer e) -> e * 2

在此代码中,主体只有一行:一个返回给定参数两次的表达式。信噪比很高,没有分号,也不需要 return 关键字。这就是一个理想的 lambda 表达式。


多行 lambda 表达式

在 Java 中,lambda 表达式的主体也可能是一个复杂的表达式或声明语句;也就是说,一个 lambda 表达式包含多行。在这种情况下,分号必不可少。如果 lambda 表达式返回一个结果,也会需要 return 关键字。下面给出了一个示例:

(Integer e) -> {
    double sqrt = Math.sqrt(e);
    double log = Math.log(e);

    return sqrt + log;
}

本示例中的 lambda 表达式返回了 sqrt 和给定参数的 log 的和。因为主体包含多行,所以括号 ( {} )、分号 ( ; ) 和 return 关键字都是必需的。


如果感觉好像 Java 因为我们编写多行 lambda 表达式而惩罚我们,—或许我们应该接受这样的暗示。


函数组合的强大功能

函数式编码风格利用了函数组合的表达能力。比较两段代码时,很容易看出富于表达的好处。第一段代码是用命令式风格编写的:

int result = 0;
for(int e : values) {
    if(e > 3 && e % 2 == 0) {
        result = e * 2;
        break;
    }
}

现在考虑用函数式风格编写的相同代码:

int result = values.stream()
    .filter(e -> e > 3)
    .filter(e -> e % 2 == 0)
    .map(e -> e * 2)
    .findFirst()
    .orElse(0);

两段代码获得了相同的结果。在命令式代码中,我们需要读入 for 循环,按照分支和中断来跟随流程。第二段代码使用了函数组合,更容易阅读一些。因为它是从上往下执行的,所以我们只需要传递该代码一次。


本质上,第二段代码读起来像是一个问题陈述: 给定一些值,仅选择大于 3 的值。从这些值中,仅选择偶数值,并将它们乘以 2。最后,挑选第一个结果。如果没有任何值存在,则返回 0。

此代码不仅优雅,而且它的 工作量并不比命令式代码多。得益于 Stream 的惰性计算能力,这里没有浪费计算资源。

函数组合的表达能力很大程度上依赖于每个 lambda 表达式的简洁性。如果您的 lambda 表达式包含多行(甚至 两行可能都太多 ),您可能没有理解函数式编程的关键点。


充满危险的长 lambda 表达式

要更好地理解编写简短的 lambda 表达式的好处,可考虑反面情况:一个包含多行代码的杂乱 lambda 表达式:

System.out.println(
values.stream()
    .mapToInt(e -> {
        int sum = 0;
        for(int i = 1; i <= e; i++) {
            if(e % i == 0) {
                sum += i;
            }
        }
        return sum;
    })
    .sum());

尽管此代码是用函数式风格编写的,但它丢失了函数式编程的优点。让我们考虑一下原因何在。


1.难以读懂

好代码应该易于读懂。此代码需要绞尽脑汁才能读懂:很难找到不同部分的开头和结尾。


2.用途不明

好代码读起来应该像一个故事,而不是像一个字谜。像这样冗长的、无特色的代码隐藏了它的具体用途,会耗费读者的时间和精力。将这段代码包装在一个命名函数中可以使其模块化,同时也可以通过相关的名称揭示它的用途。


3.代码质量差

无论您的代码有何用途,您可能都希望在某个时候重用它。这段代码的逻辑已嵌入在 lambda 表达式中,后者又以参数形式传递给另一个函数 mapToInt 。如果我们在程序的其他某个地方需要该代码,我们可能忍不住重写它,这会引起代码库中的不一致性。或者,我们也可以复制并粘贴该代码。两种选项都会得到好代码或高品质的软件。


4.难以测试

代码始终依靠键入的内容进行操作,而且不一定是我们打算执行的操作,所以这代表着必须测试任何非平凡代码。如果 lambda 表达式中的代码无法用作一个单元,则无法对它执行单元测试。您可以运行集成测试,但这无法取代单元测试,尤其是在代码执行重要工作时。


5.代码覆盖范围小

一位学习 Java 8 课程的学员最近表示他们讨厌 lambda 表达式。在我问为什么时,他们向我展示了一位同事的作品,其中包含的 lambda 表达式运行了数百行代码。嵌入在参数中的 Lambda 表达式无法轻松地作为单元提取出来,而且许多表达式在覆盖范围报告中显示为红色。由于一无所知,该团队很难假设这些代码在工作正常。


使用 Lambda 作为粘合代码

解决所有这些问题的方法是让您的 lambda 表达式高度简洁。作为第一个且非常有用的步骤,避免在 lambda 表达式中使用括号。考虑如何使用此技术轻松地重写前面杂乱的 lambda 表达式:

System.out.println(
values.stream()
    .mapToInt(e -> sumOfFactors(e))
    .sum());

此代码很简洁,尽管它不完整。该代码也具有很高的可读性。它的用途是: 给定一组值,将该列表转换为每个数的因数之和,然后计算所获得的集合的和。 显式命名以前包含在括号中的代码主体,这会让此代码变得更容易阅读和理解。函数管道现在既整洁又容易理解。


不需要解字谜,因为此代码直接表明了它的用途。计算因数之和的代码已模块化为一个名为 sumOfFactors 的单独方法,该方法可以重用。因为它是一个单独方法,所以对它的逻辑执行单元测试也很容易。因为此代码如此容易测试,所以您可以确保良好的代码覆盖范围。


简言之,曾经杂乱的 lambda 表达式现在成为了 粘合代码 — 它没有承担大量责任,只是将命名函数粘合到 mapToInt 函数。


使用方法引用进行调优

就像在本系列的其他地方看到的一样,可以通过将 lambda 表达式替换为方法引用,让上述代码更富于表达(其中 sumOfFactors 是一个名为 Sample 的类的方法):

System.out.println(
values.stream()
    .mapToInt(Sample::sumOfFactors)
    .sum());

这是重写后的 sumOfFactors 方法:

public static int sumOfFactors(int number) {
return IntStream.rangeClosed(1, number)
    .filter(i -> number % i == 0)
    .sum();
}

现在它是一个简短的方法。该方法中的 lambda 表达式也很简洁:只有一行,没有过多的繁杂过程或噪音。


结束语

简短的 lambda 表达式能提高代码可读性,这是函数式编程的重要好处之一。包含多行的 lambda 表达式具有相反的效果,会让代码变得杂乱且难以阅读。多行 lambda 表达式还难以测试和重用,这可能导致重复工作和代码质量差。幸运的是,通过将多行 lambda 表达式的主体转移到一个命名函数中,然后从 lambda 表达式内调用该函数,这样很容易避免这些问题。我也推荐尽可能将 lambda 表达式替换为方法引用。


简言之,我推荐避免多行 lambda 表达式,除非是为了演示它们为什么不好。


 
网站首页 | 专家师资 | 常见问题 | 就业案例 | 报名流程 | 联系我们
尚学堂    西安Java培训     Android培训    Java培训教程    Android培训教程    尚学堂怎么样    尚学堂学费    尚学堂视频下载
Copyright 2007 版权所有 西安雁塔尚学堂计算机学校
地址:陕西省西安市高新区科技二路西安软件园天泽大厦五楼 邮编710000 电子邮件:fanchangansxt@163.com
陕ICP备14007859号 咨询电话:029-62258374
在线咨询