11 月 19 - 20 日 Apache Pulsar 社区年度盛会来啦,立即报名! 了解详情
写点什么

Java 8 vs. Scala 之 Lambda 表达式

  • 2015-11-27
  • 本文字数:3456 字

    阅读完需:约 11 分钟

【编者的话】2014 年 3 月份众人期待已久的 Java 8 发布了,新版本从语言、编译器、类库和工具等方面对 Java 进行了诸多改进与提升,一时间风光无限;而 JVM 体系的另一门语言 Scala 则因为融合了函数式编程语言与面向对象编程语言的优点,从诞生以来就一直备受瞩目,迅速赢得了社区的强烈支持。两门语言孰优孰劣或许不能简单地做出定论,这取决于具体的应用场景、资源约束以及团队偏好等因素,但是无论作何选择首先都需要对它们有深入的了解,本文来自于 Zappos 公司 Hussachai Puripunpinyo 在 Dzone 上发表的一篇文章,介绍了他自己对 Java 和 Scala Lambda 表达式的看法

Hussachai Puripunpinyo 认为 Java 是一门静态的强类型语言,因此虽然在 Java 8 中函数已经成了一等公民,可以作为函数的参数或者返回值来传递,但是它必须要有一个类型,那就是接口,而 Lambda 表达式就是实现了 Functional 接口的对象。虽然开发人员不需要为函数对象的创建而担心,因为编译器会做这些事情,但是 Java 并没有 Scala 那么出色的类型推理机制,在 Java 中声明 Lambda 表达式必须要指定目标类型。考虑到 Java 必须维持向后兼容性,这样做也是可以让人接受和理解的,事实上在兼容性方面 Java 已经做的足够好了,例如 Thread.stop() 在 JDK 1.0 中就已经有了,虽然被标记为“已废弃”数十年,但是现在依然存在,因而不应该因为其他语言有更好的语法就期望 Java 快速地改变自己的语法。

为了支持 Lambda 表达式 Java 引入了函数式接口,该接口只有一个抽象方法。@FunctionalInterface 是一个注解,用来表明被注解的接口是一个函数式接口,该注解是可选的,只有需要对接口是否符合契约做检查的时候才需要使用。

在 Java 中,Lambda 表达式必须要有一个类型,而且类型必须有且仅有一个抽象方法,而大部分已有的回调接口已经满足这一需求,因此用户不需要对它们做出任何改变就能够重用这些接口。例如:

复制代码
// 在 Java 8 之前
Runnable r = new Runnable(){
public void run(){
System.out.println(“This should be run in another thread”);
}
};
//Java 8
Runnable r = () -> System.out.println(“This should be run in another thread”);

对于有一个参数并且有返回值的函数,Java 8 提供了一组通用的函数式接口,这些接口在 java.util.function 包中,使用方式如下:

复制代码
//Java 8
Function<string integer=""> parseInt = (String s) -> Integer.parseInt(s);
</string>

因为参数类型可以从 Function 对象的类型声明中推断出来,所以类型和小括号都可以省略:

复制代码
//Java 8
Function<string integer=""> parseInt = s -> Integer.parseInt(s);
</string>

对于需要两个参数的函数,Java 8 提供了 BiFunction:

复制代码
//Java 8
BiFunction<integer integer=""> multiplier =
(i1, i2) -> i1 * i2; //you can’t omit parenthesis here!
</integer>

对于需要 3 个及以上参数的接口,Java 8 并没有提供相应的 TriFunction、QuadFunction 等定义,但是用户可以定义自己的 TriFunction,如下:

复制代码
//Java 8
@FunctionalInterface
interface TriFunction<a b="" c="" r=""> {
public R apply(A a, B b, C c);
}
</a>

在引入了之前定义好的接口之后就可以这样声明 Lambda 表达式:

复制代码
//Java 8
TriFunction<integer integer=""> sumOfThree
= (i1, i2, i3) -> i1 + i2 + i3;
</integer>

对于语言的设计者为什么会止步于 BiFunction,Hussachai Puripunpinyo 认为 TriFunction、QuadFunction 等需要更多参数的接口需要太多的类型声明,接口的定义变得非常长,同时又怎么决定定义到哪一个才最合适呢,总不能一直定义到包含 9 个参数和一个返回值类型的 EnnFunction 吧!

以上示例显示参数越多,类型定义越冗长,甚至可能整整一行都是类型声明,那么必须要声明类型么?答案是在 Java 中必须如此,但是在 Scala 中就简单的多了。

Scala 也是一门静态强类型的语言,但是它从诞生开始就是一门函数式语言,完美融合了面向对象范式和函数式语言范式。Scala 中的 Lambda 表达式也有一个类型,但是语言的设计者采用了数字而不是拉丁语来命名,Scala 为开发者提供了 0 到 22 个参数的接口定义(Function0、Function1、… Function22),如果需要更多的参数,那么或许是开发者在设计上就存在问题。在 Scala 中 Function 的类型是特性(trait),类似于 Java 中的抽象类。

Scala 中的 Runnable 示例与 Java 中的实现方式不同:

复制代码
//Scala
Future(println{“This should be run in another thread”})
// 以上代码等同于
//Java 8
//assume that you have instantiated ExecutorService beforehand.
Runnable r = () -> System.out.println(“This should be run in another thread”);
executorService.submit(r);

在 Scala 中声明一个 Lambda 表达式不必像 Java 那样必须显式指定类型,而且方式也有很多:

复制代码
//Java 8
Function<string integer=""> parseInt = s -> Integer.parseInt(s);
//Scala
val parseInt = (s: String) => s.toInt
//or
val parseInt:String => Int = s => s.toInt
//or
val parseInt:Function1[String, Int] = s => s.toInt
</string>

如果需要更多的参数:

复制代码
//Java 8
PentFunction<integer integer=""> sumOfFive
= (i1, i2, i3, i4, i5) -> i1 + i2 + i3 + i4 + i5;
//Scala
val sumOfFive = (i1: Int, i2: Int, i3: Int, i4: Int, i5: Int) =>
i1 + i2 + i3 + i4 + i5;
</integer>

可以看到,Scala 的语法更简洁,可读性更好,开发者不需要声明接口类型,通过参数列表中的类型就能看出对象的类型。

复制代码
//Java 8
PentFunction<string boolean="" double="" integer="" string="">
sumOfFive = (i1, i2, i3, i4, i5) -> i1 + i2 + i3 + i4 + i5;
//Scala
val sumOfFive = (i1: String, i2: Int, i3: Double, i4: Boolean, i5: String)
=> i1 + i2 + i3 + i4 + i5;
</string>

对于上面这段代码,开发者一打眼就能看出 i3 是 Double 类型的,但是在 Java 8 中开发者必须要数一数才能看出来,如果要在 Java 8 中达到这种效果,那只有从格式上来做文章了:

复制代码
//Java 8
PentFunction sumOfFive
= (Integer i1, String i2, Integer i3, Double i4, Boolean i5)
-> i1 + i2 + i3 + i4 + i5;

但是这真是非常糟糕,开发者必须一次次地键入类型,另外,Java 8 并没有定义 PentFunction,你还必须自己定义:

复制代码
//Java 8
@FunctionalInterface
interface PentFunction<a b="" c="" d="" e="" r=""> {
public R apply(A a, B b, C c, D d, E e);
}
</a>

Hussachai Puripunpinyo 认为 Scala 在函数式方面做的更好,一方面是因为 Scala 本身就是一门函数式语言,另一方面是因为 Java 语言的设计者在引入新东西的时候必须要考虑兼容性,因而有很多约束。但是即使如此,Java 8 依然引入了一些非常酷的特性,例如方法引用,该特性就能够让 Lambda 表达式的声明更加简短:

复制代码
//Java 8
Function<string integer=""> parseInt = s -> Integer.parseInt(s);
// 使用方法引用可以简写为:
//Java 8
Function<string integer=""> parseInt = Integer::parseInt;
</string></string>

在 Java 8 中,方法引用的构建规则有 3 种:

  1. (args) -> ClassName.staticMethod(args);
    可以重写为ClassName::staticMethod;
    Function<integer string=""> intToStr = String::valueOf;</integer>3. (instance, args) -> instance.instanceMethod(args);
    可以重写为ClassName::instanceMethod;
复制代码
BiFunction<string integer="" string=""> indexOf = String::indexOf;
</string>
  1. (args) -> expression.instanceMethod(args);
    可以重写为expression::instanceMethod;
复制代码
Function<string integer=""> indexOf = new String()::indexOf;
</string>

流 API 就大量使用了这种语法来简化代码的编写:

复制代码
pets.stream().map(Pet::getName).collect(toList());
// The signature of map() function can be derived as
// <string> Stream<string> map(Function super Pet, ? extends String> mapper)
</string></string>

编后语

《他山之石》是 InfoQ 中文站新推出的一个专栏,精选来自国内外技术社区和个人博客上的技术文章,让更多的读者朋友受益,本栏目转载的内容都经过原作者授权。文章推荐可以发送邮件到 editors@cn.infoq.com。


感谢徐川对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群(已满),InfoQ 读者交流群(#2))。

2015-11-27 18:004658
用户头像

发布了 321 篇内容, 共 110.9 次阅读, 收获喜欢 16 次。

关注

评论

发布
暂无评论
发现更多内容

【编程实践】提高工作效率,避免重复且枯燥的操作,利用Python自动发送邮件

迷彩

SMTP 邮件协议 9月月更 Python邮件发送

【云原生 | 从零开始学Kubernetes】八、命名空间资源配额以及标签

泡泡

Docker 云计算 云原生 k8s 9月月更

2022年企业Java面试前复习的正确姿势(已助力512人入职大厂)

收到请回复

Java 程序员 微服务 语言 & 开发

20道常被问到的JavaScript题目

helloworld1024fd

JavaScript 前端

面试突击86:SpringBoot 事务不回滚?怎么解决?

王磊

Java 面试

Javaweb核心响应对象

楠羽

笔记 Java核心笔记 9月月更

数据中台架构概述

穿过生命散发芬芳

数据中台 9月月更

2022前端面试官经常会考什么

beifeng1996

前端 React

【算法实践】手把手带你简单实现希尔排序

迷彩

算法 排序算法 希尔排序 算法实践 9月月更

总结了一下前端高频面试题的答案

loveX001

JavaScript 前端

Chrome操作指南——入门篇(十四)drawer

Augus

Chrome开发者工具 九月月更

全网首次公开!阿里巴巴1685页Java面试突击核心讲(基础到高级足足涵盖19个Java核心技术)

Java永远的神

数据库 spring 程序员 程序人生 java面试

【云原生 | 从零开始学Kubernetes】九、k8s的node节点选择器与node节点亲和性

泡泡

Docker 云计算 云原生 k8s 9月月更

大厂前端面试考什么?

loveX001

JavaScript 前端

如何准备vue相关的知识点

bb_xiaxia1998

Vue 前端

信息论与编码(一)| 信源分类与数学模型

timerring

9月日更 信息熵

阿里P8大牛总结的Java锁机制入门笔记,堪称教科书式天花板

收到请回复

多线程 语言 & 开发

第九期-模块一

wuli洋

2022-09-25:给定一个二维数组matrix,数组中的每个元素代表一棵树的高度。 你可以选定连续的若干行组成防风带,防风带每一列的防风高度为这一列的最大值 防风带整体的防风高度为,所有列防风高度

福大大架构师每日一题

算法 rust 福大大

这些js手写题你能回答上来几道

helloworld1024fd

JavaScript 前端

MFC|实现自定义复选框效果

中国好公民st

c++ 控件 9月月更

Canvas+Javascript实现点击小球的爆炸效果

Sam9029

JavaScript canvas 9月月更 小球爆炸

微信业务架构图与学生管理系统架构图

冷夫冲

架构实战营 #架构实战营 架构师实战营 「架构实战营」

Python语法之字典

向阳逐梦

字典 9月月更 Python语法

监控平台SkyWalking9入门实践

知了一笑

Java 架构 Skywalking

docker的/var/run/docker.sock参数

程序员欣宸

Docker Docker 镜像 9月月更

Cookie和Session详解

共饮一杯无

session Cookie 9月月更 客户端服务器端

Linux安装minikube指南

程序员欣宸

Kubernetes minikube 9月月更

大数据ELK(四):Lucene的美文搜索案例

Lansonli

9月月更 EKL

react面试应该准备哪些题目

beifeng1996

前端 React

Java 8 vs. Scala之Lambda表达式_Scala_孙镜涛_InfoQ精选文章