Java 为什么需要 Lambda 表达式

发布于 2021-03-15 12:38:11 字数 3619 浏览 1447 评论 0

Lambda 表达式即将出现在 Java8 中,但似乎还是遇到了些阻力,并不是所有 Java 开发者都愿意买账,他们觉得添加函数式编程(参考 )的特性到 Java 中本身就是一种错误,因为这样不得不在Java所擅长的面向对象和指令式编程(参考 )中做出妥协。这篇文章的目的就是希望能用一些直白的例子来明确阐述 Lambda 的必要性以及打消部分开发者的顾虑---作为一门流行大众化编程语言支持Lambda表达式的的必要性。

外部与内部迭代

先来看一个简单的例子:

List<Interger> numbers = Arrays.asList(1,2,3,4,5,6);

迭代所有元素并打印结果:

for (int number : numbers){
    System.out.println(number);
}

(此处省略68字,俺实在是不知道作者说的啥,如果你看明白了,不妨在评论里告诉我,谢谢!)下面是作者与他两岁的女儿在玩玩具后的一段对话:

我:“Sofia,我们把完全收好吧,地上还有玩具吗?” Sofia:“好的,还有一个球” 我:“嗯,把球放到盒子里去,还有其它东西吗?” Sofia:“有,还有我的洋娃娃” 我:“好吧,把洋娃娃也放进盒子里,还有吗?” Sofia:“还有一本书” 我:“把书也放进去,还有吗” Sofia:“没有了” 我:“非常好,任务完成了”

这是我们天用着Java集合做的事情,但我们已不是两岁的小孩了,从外部迭代集合,逐个取出来处理,如果这样告诉Sofia:“把所有的玩具放进盒子里”,这种方式会更好。另外两个原因,为什么内部迭代更好呢?首先,Sofia可以同时选择一手拿娃娃,一手拿球,第二,他可以优先把最近的玩具放进盒子里。同理,使用内部迭代,JIT编译器可以并行处理元素达到优化或者按照不同的顺序处理。这些优化效果要是放在外部迭代那是没法实现的。

为什么不在内部迭代?我认为这仅仅只是糟糕的心理习惯在作祟,Java集合框架中缺乏这样的支持。在Java8预览版中可以创建匿名内部类,如下:

numbers.forEach(new Consumer<Interger>(){
    public void accept(Integer value){
        System.out.print(value);
    }
});

重点来了....

事实上,forEach方法和Consumer接口已经添加到Java8中来了,但是你可以在Java5+中使用类似guava或者lambdaj做一些类似的操作,然而lambda表达试可以用更少的代码和易读的方式实现相同的结果:

numbers.forEach((Integer value) -> System.out.println(value));

lambda表达式由两部份组成,箭头左边是参数部分右边是函数体,在这个例子中编译器自动计算出lambda表达式有相同的签名唯一没有实现的接口consumer(这就叫函数结果的原因),即使声称的直接骂可以潜在不同,类型声明lambda表达式参数可以,最大的部分

numbers.forEach(value -> System.out.println(value));

但我们可以重写写它,最后一条语句甚至可以更简洁地使用方法引用,Java8中另一个特性:可以使用::操作符引用一个静态或实例方法:

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

以这种方式,函数式编程因eta扩展而闻名。方法的名字被编译器expaned被编译器。

不仅仅传递值,还有行为

我们看到前面些例子主要可能的原因为什么lambda表达式如此有用,传递一个lambda表达式给另一个方法,允许我们传递的不仅仅是值,还包括行为,这使得戏剧性的上升到我们抽象的级别,更加泛型,灵活可用的API,让我们看一个更深的例子:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

我们请求些一个方法,所有整数的和:

public int sumAll(List<Integer> numbers) {
    int total = 0;
    for (int number : numbers) {
        total += number;
    }
    return total;
}

没过几天,经理跑过来告诉我们说,因业务需求还需要要一个函数求偶数项的和,因此什么样的方法是最快的呢?很简单,只要拷贝粘贴前面那个方法就行了:

public int sumAllEven(List<Integer> numbers) {
    int total = 0;
    for (int number : numbers) {
        if (number % 2 == 0) {
            total += number;
        }
    }
    return total;
}

又有一天,另外一个需求来了,这次他们需要求和只对大于3的数求和。那怎么做呢?我们再一次拷贝粘贴代码调整条件,但是这样感觉代码很乱,不是吗?现在,我们可以“First write,Second Copy,ThirdRefactor"原则,式时候考虑一个更聪明的更通用的方法去处理了。在这个例子中实现了一个更高顺序函数接受列表,还有断言(另一个函数式接口java8中出现的),定义了如何过滤。

public int sumAll(List<Integer> numbers, Predicate<Integer> p) {
    int total = 0;
    for (int number : numbers) {
        if (p.test(number)) {
            total += number;
        }
    }
    return total;
}

换句话说,我们传递方法不仅仅是这些数据,而且还有行为,定义如何使用他们。这样方法我们可以满足所有3种需求用一个更通用可重用的方法

sumAll(numbers, n -> true);
sumAll(numbers, n -> n % 2 == 0);
sumAll(numbers, n -> n > 3);

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84961 人气
更多

推荐作者

书间行客

文章 0 评论 0

神妖

文章 0 评论 0

undefined

文章 0 评论 0

38169838

文章 0 评论 0

彡翼

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文