博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java 8 vs. Scala之Lambda表达式
阅读量:6671 次
发布时间:2019-06-25

本文共 4943 字,大约阅读时间需要 16 分钟。

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

\\

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 = () -\u0026gt; System.out.println(“This should be run in another thread”);\
\\

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

\\
\//Java 8\Function parseInt = (String s) -\u0026gt; Integer.parseInt(s);\
\\

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

\\
\//Java 8\Function parseInt = s -\u0026gt; Integer.parseInt(s);\
\\

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

\\
\//Java 8\BiFunction multiplier = \  (i1, i2) -\u0026gt; i1 * i2; //you can’t omit parenthesis here!\
\\

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

\\
\//Java 8\@FunctionalInterface\interface TriFunction {  \  public R apply(A a, B b, C c);\}\
\\

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

\\
\//Java 8\TriFunction sumOfThree \  = (i1, i2, i3) -\u0026gt; i1 + i2 + i3;\
\\

对于语言的设计者为什么会止步于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 = () -\u0026gt; System.out.println(“This should be run in another thread”);\executorService.submit(r);\
\\

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

\\
\//Java 8\Function parseInt = s -\u0026gt; Integer.parseInt(s);\\//Scala\val parseInt = (s: String) =\u0026gt; s.toInt\//or\val parseInt:String =\u0026gt; Int = s =\u0026gt; s.toInt\//or\val parseInt:Function1[String, Int] = s =\u0026gt; s.toInt\\
\\

如果需要更多的参数:

\\
\//Java 8\PentFunction sumOfFive \  = (i1, i2, i3, i4, i5) -\u0026gt; i1 + i2 + i3 + i4 + i5;\\//Scala\val sumOfFive = (i1: Int, i2: Int, i3: Int, i4: Int, i5: Int) =\u0026gt; \  i1 + i2 + i3 + i4 + i5;\
\\

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

\\
\//Java 8\PentFunction \  sumOfFive = (i1, i2, i3, i4, i5) -\u0026gt; i1 + i2 + i3 + i4 + i5;\\//Scala\val sumOfFive = (i1: String, i2: Int, i3: Double, i4: Boolean, i5: String) \=\u0026gt; i1 + i2 + i3 + i4 + i5;\
\\

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

\\
\//Java 8 \PentFunction sumOfFive \= (Integer i1, String i2, Integer i3, Double i4, Boolean i5) \-\u0026gt; i1 + i2 + i3 + i4 + i5;\
\\

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

\\
\//Java 8\@FunctionalInterface\interface PentFunction {  \  public R apply(A a, B b, C c, D d, E e);\}\
\\

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

\\
\//Java 8\Function parseInt = s -\u0026gt; Integer.parseInt(s);\//使用方法引用可以简写为:\//Java 8\Function parseInt = Integer::parseInt;\
\\

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

\\
  1. (args) -\u0026gt; ClassName.staticMethod(args);\\
    可以重写为ClassName::staticMethod;\\
Function intToStr = String::valueOf;
\\
(instance, args) -\u0026gt; instance.instanceMethod(args);\\
可以重写为
ClassName::instanceMethod;\\
BiFunction indexOf = String::indexOf;\
\\
(args) -\u0026gt; expression.instanceMethod(args);\\
可以重写为
expression::instanceMethod;\\
\Function indexOf = new String()::indexOf;\
\

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

\\
\pets.stream().map(Pet::getName).collect(toList());\// The signature of map() function can be derived as\//  Stream map(Function super Pet, ? extends String\u0026gt; mapper)\
\\

编后语

\\

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

\\

感谢对本文的审校。

\

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

你可能感兴趣的文章
launchpad, jira, github
查看>>
JavaWeb学习笔记——XML和SAX解析区别
查看>>
hdu1716排列2(stl:next_permutation+优先队列)
查看>>
Java 8 时间日期库的20个使用示例
查看>>
Android系统开发(4)——Autotools
查看>>
Nginx教程(一) Nginx入门教程
查看>>
【cocos2d-x 3.7 飞机大战】 决战南海I (十) 游戏主场景
查看>>
ORM进阶:Hibernate框架搭建及开发
查看>>
scala Wordcount
查看>>
单细胞文献分析 Quantitative single-cell rna-seq with unique molecular identifers
查看>>
面试2
查看>>
国庆第三天如何避免无聊
查看>>
Java多线程之细说线程池
查看>>
【274】Python 相关问题
查看>>
Linux-进程间的通信-信号集函数【转】
查看>>
js2word/html2word的简单实现
查看>>
jQuery.extend和jQuery.fn.extend的区别?
查看>>
职业发展
查看>>
Linux下环境变量设置
查看>>
phonegap 安装和使用eclipse
查看>>