【编者按】虽然 Java 深得大量开发者喜爱,但是对比其他现代编程语言,其语法确实略显冗长。但是通过 Java8,直接利用 lambda 表达式就能编写出既可读又简洁的代码。作者 Hussachai Puripunpinyo 的软件工程师,作者通过对比 Java 8和 Scala,对性能和表达方面的差异进行了分析,并且深入讨论关于 Stream API 的区别,本文由 OneAPM 工程师编译整理。

数年等待,Java 8 终于添加了高阶函数这个特性。本人很喜欢 Java,但不得不承认,相比其他现代编程语言,Java 语法非常冗长。然而通过 Java8,直接利用 lambda 表达式就能编写出既可读又简洁的代码(有时甚至比传统方法更具可读性)。

Java 8于2014年3月3日发布,但笔者最近才有机会接触。因为笔者也很熟悉 Scala,所以就产生了对比 Java 8和Scala 在表达性和性能方面的差异,比较将围绕 Stream API 展开,同时也会介绍如何使用 Stream API 来操作集合。

由于文章太长,所以分以下三个部分详细叙述。

Part 1.Lambda 表达式

Part 2. Stream API vs Scala collection API

Part 3. Trust no one, bench everything(引用自sbt-jmh)

首先,我们来了解下 Java 8的 lambda 表达式,虽然不知道即使表达式部分是可替代的,他们却称之为 lambda 表达式。这里完全可以用声明来代替表达式,然后说 Java 8还支持 lambda 声明。编程语言将函数作为一等公民,函数可以被作为参数或者返回值传递,因为它被视为对象。Java是一种静态的强类型语言。所以,函数必须有类型,因此它也是一个接口。

另一方面,lambda 函数就是实现了函数接口的一个类。无需创建这个函数的类,编译器会直接实现。不幸的是,Java 没有 Scala 那样高级的类型接口。如果你想声明一个 lambda 表达式,就必须指定目标类型。实际上,由于 Java 必须保持向后兼容性,这也是可理解的,而且就目前来说 Java 完成得很好。例如,Thread.stop() 在 JDK 1.0版时发布,已过时了十多年,但即便到今天仍然还在使用。所以,不要因为语言 XYZ 的语法(或方法)更好,就指望 Java 从根本上改变语法结构。

所以,Java 8的语言设计师们奇思妙想,做成函数接口!函数接口是只有一个抽象方法的接口。要知道,大多数回调接口已经满足这一要求。因此,我们可以不做任何修改重用这些接口。@FunctionalInterface 是表示已注释接口是函数接口的注释。此注释是可选的,除非有检查要求,否则不用再进行处理。

请记住,lambda 表达式必须定义类型,而该类型必须只有一个抽象方法。

  1. //Before Java 8
  2. Runnable r = new Runnable(){
  3. public void run(){
  4. System.out.println(“This should be run in another thread”);
  5. }
  6. };
  7. //Java 8
  8. Runnable r = () -> System.out.println(“This should be run in another thread”);

如果一个函数有一个或多个参数并且有返回值呢?为了解决这个问题,Java 8提供了一系列通用函数接口,在java.util.function包里。

  1. //Java 8
  2. Function<String, Integer> parseInt = (String s) -> Integer.parseInt(s);

该参数类型可以从函数中推断,就像 Java7中的diamond operator,所以可以省略。我们可以重写该函数,如下所示:

  1. //Java 8
  2. Function<String, Integer> parseInt = s -> Integer.parseInt(s);

如果一个函数有两个参数呢?无需担心,Java 8 中有 BiFunction。

  1. //Java 8
  2. BiFunction<Integer, Integer, Integer> multiplier =
  3. (i1, i2) -> i1 * i2; //you can’t omit parenthesis here!

如果一个函数接口有三个参数呢?TriFunction?语言设计者止步于 BiFunction。否则,可能真会有 TriFunction、quadfunction、pentfunction 等。解释一下,笔者是采用 IUPAC 规则来命名函数的。然后,可以按如下所示定义 TriFunction。

  1. //Java 8
  2. @FunctionalInterface
  3. interface TriFunction<A, B, C, R> {
  4. public R apply(A a, B b, C c);
  5. }

然后导入接口,并把它当作 lambda 表达式类型使用。

  1. //Java 8
  2. TriFunction<Integer, Integer, Integer, Integer> sumOfThree
  3. = (i1, i2, i3) -> i1 + i2 + i3;

这里你应该能理解为什么设计者止步于 BiFunction。

如果还没明白,不妨看看 PentFunction,假设我们在其他地方已经定义了 PentFunction。

  1. //Java 8
  2. PentFunction<Integer, Integer, Integer, Integer, Integer, Integer>
  3. sumOfFive = (i1, i2, i3, i4, i5) -> i1 + i2 + i3 + i4 + i5;

你知道 Ennfunction 是多长吗?(拉丁语中,enn 表示9)你必须申报 10 种类型(前 9 个是参数,最后一个是返回类型),大概整行都只有类型了。那么声明一个类型是否有必要呢?答案是肯定的。(这也是为什么笔者认为 Scala 的类型接口比 Java 的更好)

Scala 也有其 lambda 表达式类型。在 Scala 中,你可以创建有22个参数的 lambda 表达式,意味着 Scala 有每个函数的类型(Function0、Function1、……Function22)。函数类型在 Scala 函数中是一个 Trait,Trait 就像 Java 中的抽象类,但可以当做混合类型使用。如果还需要22个以上的参数,那大概是你函数的设计有问题。必须要考虑所传递的一组参数的类型。在此,笔者将不再赘述关于 Lambda 表达式的细节。

下面来看看Scala的其他内容。Scala 也是类似 Java 的静态强类型语言,但它一开始就是函数语言。因此,它能很好地融合面向对象和函数编程。由于 Scala 和 Java 所采用的方法不同,这里不能给出 Runnable 的 Scala 实例。Scala 有自己解决问题的方法,所以接下来会详细探讨。

//Scala

Future(println{“This should be run in another thread”})

与以下 Java8 的代码等效。

//Java 8

//assume that you have instantiated ExecutorService beforehand.

Runnable r = () -> System.out.println(“This should be run in another thread”);

executorService.submit(r);

如果你想声明一个 lambda 表达式,可以不用像 Java 那样声明一个显式类型。

  1. //Java 8
  2. Function<String, Integer> parseInt = s -> Integer.parseInt(s);
  3. //Scala
  4. val parseInt = (s: String) => s.toInt
  5. //or
  6. val parseInt:String => Int = s => s.toInt
  7. //or
  8. val parseInt:Function1[String, Int] = s => s.toInt

所以,在 Scala 中的确有多种办法来声明类型。让编译器来执行。那么 PentFunction 呢?

  1. //Java 8
  2. PentFunction<Integer, Integer, Integer, Integer, Integer, Integer> sumOfFive
  3. = (i1, i2, i3, i4, i5) -> i1 + i2 + i3 + i4 + i5;
  4. //Scala
  5. val sumOfFive = (i1: Int, i2: Int, i3: Int, i4: Int, i5: Int) =>
  6. i1 + i2 + i3 + i4 + i5;

Scala 更短,因为不需要声明接口类型,而整数类型在 Scala 中是 int。短不总意味着更好。Scala 的方法更好,不是因为短,而是因为更具可读性。类型的上下文在参数列表中,可以很快找出参数类型。如果还不确定,可以再参考以下代码。

  1. //Java 8
  2. PentFunction<String, Integer, Double, Boolean, String, String>
  3. sumOfFive = (i1, i2, i3, i4, i5) -> i1 + i2 + i3 + i4 + i5;
  4. //Scala
  5. val sumOfFive = (i1: String, i2: Int, i3: Double, i4: Boolean, i5: String)
  6. => i1 + i2 + i3 + i4 + i5;

在 Scala 中,可以很明确地说出 i3 类型是 Double 型,但在 Java 8 中,还需要算算它是什么类型。你可能争辩说 Java 也可以,但出现这样的状况:

  1. //Java 8
  2. PentFunction<Integer, String, Integer, Double, Boolean, String> sumOfFive
  3. = (Integer i1, String i2, Integer i3, Double i4, Boolean i5)
  4. -> i1 + i2 + i3 + i4 + i5;

你必须一遍又一遍的重复下去。

除此之外,Java8 并没有 PentFunction,需要自己定义。

  1. //Java 8
  2. @FunctionalInterface
  3. interface PentFunction<A, B, C, D, E, R> {
  4. public R apply(A a, B b, C c, D d, E e);
  5. }

是不是意味着 Scala 就更好呢?在某些方面的确是。但也有很多地方 Scala 不如 Java。所以很难说到底哪种更好,我之所以对两者进行比较,是因为 Scala 是一种函数语言,而 Java 8 支持一些函数特点,所以得找函数语言来比较。由于 Scala 可以运行在 JVM 上,用它来对比再好不过。可能你会在使用函数时,Scala 有更简洁的语法和方法,这是因为它本来就是函数语言,而 Java 的设计者在不破坏之前的基础上拓展设计,显然会有更多限制。

尽管 Java在语法上与 lambda 表达式相比有一定局限性,但 Java8 也引进了一些很酷的功能。例如,利用方法引用的特性通过重用现有方法使得编写 lambda 表达式更简洁。更简洁吗???

  1. //Java 8
  2. Function<String, Integer> parseInt = s -> Integer.parseInt(s);

可以使用方法引用来重写函数,如下所示

  1. //Java 8
  2. Function<String, Integer> parseInt = Integer::parseInt;

还可以通过实例方法来使用方法引用。之后会在第二部分的 Stream API 中指出这种方法的可用性。

方法引用的构造规则

1.(args) -> ClassName.staticMethod(args);

可以像这样重写ClassName::staticMethod;

Function<Integer, String> intToStr = String::valueOf;

2.(instance, args) -> instance.instanceMethod(args);

可以像这样重写 ClassName::instanceMethod;

BiFunction<String,String, Integer> indexOf = String::indexOf;

3.(args) -> expression.instanceMethod(args);

可以像这样重写 expression::instanceMethod;

Function<String, Integer>indexOf = new String()::indexOf;

你有没有注意到规则2有点奇怪?有点混乱?尽管 indexOf 函数只需要1个参数,但 BiFunction 的目标类型是需要2个参数。其实,这种用法通常在 Stream API 中使用,当看不到类型名时才有意义。

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

从规则3中,你可能会好奇能否用 lambda 表达式替换 new String()?

你可以用这种方法构造一个对象

Supplier<String> str =String::new;

那么可以这样做吗?

Function<Supplier<String>,Integer> indexOf = (String::new)::indexOf;

不能。它不能编译,编译器会提示The target type of this expression must be a functional interface。错误信息很容易引起误解,而且似乎 Java 8通过泛型参数并不支持类型接口。即使使用一个 Functionalinterface 的实例(如前面提到的「STR」),也会出现另一个错误The type Supplier<String> does not define indexOf(Supplier<String>) that is applicable here。String::new 的函数接口是 Supplier,而且它只有方法命名为 get()。indexOf 是一个属于 String 对象的实例方法。因此,必须重写这段代码,如下所示。

  1. //Java
  2. Function<String, Integer> indexOf = ((Supplier<String>)String::new).get()::indexOf;

Java 8 是否支持 currying (partial function)?

的确可行,但你不能使用方法引用。你可以认为是一个 partial 函数,但是它返回的是函数而不是结果。接着将要介绍使用 currying 的简单实例,但这个例子也可能行不通。在传递到函数之前,我们通常进行参数处理。但无论如何,先看看如何利用 lambda 表达式实现 partial 函数。假设你需要利用 currying 实现两个整数相加的函数。

  1. //Java
  2. IntFunction<IntUnaryOperator>add = a -> b -> a + b;
  3. add.apply(2).applyAsInt(3);//the result is 4! I'm kidding it's 5.

该函数可以同时采用两个参数。

  1. //Java
  2. Supplier<BiFunction<Integer,Integer, Integer>> add = () -> (a, b) -> a + b;
  3. add.get().apply(2, 3);

现在,可以看看 Scala 方法。

  1. //Scala
  2. val add = (a: Int) => (b:Int) => a + b
  3. add(1)(2)
  4. //Scala
  5. val add = () => (a: Int,b: Int) => a + b
  6. add2()(1,2)

因为类型引用和语法糖,Scala 的方法比 Java 更简短。在 Scala 中,你不需要在 Function trait 上调用 apply 方法,编译器会即时地将()转换为 apply 方法。

原文链接: https://dzone.com/articles/java-8-λe-vs-scalapart-i

OneAPM for Java 能够深入到所有 Java 应用内部完成应用性能管理和监控,包括代码级别性能问题的可见性、性能瓶颈的快速识别与追溯、真实用户体验监控、服务器监控和端到端的应用性能管理。想阅读更多技术文章,请访问 OneAPM 官方博客

Java 8 vs. Scala(一): Lambda表达式的更多相关文章

  1. Java 终于在 Java 8 中引入了 Lambda 表达式。也称之为闭包或者匿名函数。

    本文首发于 blog.zhaochunqi.com 转载请注明 blog.zhaochunqi.com 根据JSR 335, Java 终于在 Java 8 中引入了 Lambda 表达式.也称之为闭 ...

  2. Java 8新特性-3 Lambda 表达式

    在 Java 8 之前,匿名内部类,监听器和事件处理器的使用都显得很冗长,代码可读性很差. 在Java 8 之前使用匿名内部类: 例如 interface ITestPrint{ public voi ...

  3. [Java] 设计模式:代码形状 - lambda表达式的一个应用

    [Java] 设计模式:代码形状 - lambda表达式的一个应用 Code Shape 模式 这里介绍一个模式:Code Shape.没听过,不要紧,我刚刚才起的名字. 作用 在应用程序的开发中,我 ...

  4. Java 8 新特性之 Lambda表达式

    Lambda的出现就是为了增强Java面向过程编程的深度和灵活性.今天就来分享一下在Java中经常使用到的几个示例,通过对比分析,效果应该会更好. – 1.实现Runnable线程案例 其存在的意义就 ...

  5. Effective Java 第三版——42.lambda表达式优于匿名类

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  6. Java编程的逻辑 (91) - Lambda表达式

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

  7. Java之线程池和Lambda表达式

    线程池和lambda表达式 学习线程池和lambda表达式的理解 补充一个知识点(单例设计模式) 在多线程中,我们只需要一个任务类,为了防止创建多个任务类,这个时候就需要用到单例模式,单例模式有两种设 ...

  8. Java 常用的几个lambda表达式

    Lambda表达式是Java 8一个非常重要的新特性.它像方法一样,利用很简单的语法来定义参数列表和方法体.目前Lambda表达式已经成为高级编程语言的标配,像Python,Swift,C#等都已经支 ...

  9. java成神之——properties,lambda表达式,序列化

    Properties 加载defaults.properties文件 写Properties到xml文件 读Properties从xml文件 Lambda表达式 自定义 内置 sort方法中使用Lam ...

  10. Java 8 为什么会引入lambda 表达式?

    Java 8 为什么会引入lambda ? 在Java8出现之前,如果你想传递一段代码到另一个方法里是很不方便的.你几乎不可能将代码块到处传递,因为Java是一个面向对象的语言,因此你要构建一个属于某 ...

随机推荐

  1. php如何判断是否为json数据(格式)

    首先要记住json_encode返回的是字符串, 而json_decode返回的是对象. 判断数据不是JSON格式:  代码如下 复制代码 function is_not_json($str){    ...

  2. 第三十二篇、iOS 10开发

    1.语音识别 苹果官方在文档中新增了API   Speech,那么在以前我们处理语音识别非常的繁琐甚至很多时候可能需要借助于第三方框架处理,那么苹果推出了这个后,我们以后处理起来就非常的方便了,spe ...

  3. 第二十八篇、自定义的UITableViewCell上有图片需要显示,要求网络网络状态为WiFi时,显示图片高清图;网络状态为蜂窝移动网络时,显示图片缩略图

    1)SDWebImage会自动帮助开发者缓存图片(包括内存缓存,沙盒缓存),所以我们需要设置用户在WiFi环境下下载的高清图,下次在蜂窝网络状态下打开应用也应显示高清图,而不是去下载缩略图. 2)许多 ...

  4. 【转载】应读者强烈要求给出《超容易的Linux系统管理入门书》一书的主要知识点

    刚开始了一篇连载,收到广大Linux爱好者的反馈,非常欣慰.大家对Linux学习感到很迷茫,不知道学哪些内容,如何学习? <超容易的Linux系统管理入门书>一书是腾讯Linux专家在腾讯 ...

  5. Git初始化与上传

    一: 现在git上Create个repository 二:进入要长传的工程目录打开git bash git initgit statusgit add .//add .的时候文件不要被占用. git ...

  6. Poj OpenJudge 百练 1573 Robot Motion

    1.Link: http://poj.org/problem?id=1573 http://bailian.openjudge.cn/practice/1573/ 2.Content: Robot M ...

  7. java.util.ArrayList源码分析

    public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess ...

  8. Spring零碎知识复习

    自学了Spring也有一段时间了,多多少少掌握了一些Spring的知识,现在手上也没有很多的项目练手,就将就着把这些学到的东西先收集起来,方便日后用到的时候没地方找. 1.spring的国际化 主要是 ...

  9. 带搜索的下拉框Chosen

    一:参考 https://harvesthq.github.io/chosen/ Chosen是一个jQuery插件 二:引入js文件 <link href="plug-in/chos ...

  10. 使用GruntJS构建Web程序

    Gruntjs是JavaScript项目的构建工具,也是基于node的一个命令行工具.很多开源JS项目都是使用它搭建.如jQuery.Qunit.CanJS等.它有以下作用 合并JS文件 压缩JS文件 ...