Java 8 vs. Scala: Lambda表达式

Posted CSDN云计算

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 8 vs. Scala: Lambda表达式相关的知识,希望对你有一定的参考价值。

Java 8 vs. Scala(一): 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表达式必须定义类型,而该类型必须只有一个抽象方法。

//Before 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);

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

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

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

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

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

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

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

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

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

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

//Java 8
PentFunction<Integer, Integer, Integer, Integer, Integer, Integer> 
  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那样声明一个显式类型。

//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

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

//Java 8
PentFunction<Integer, Integer, Integer, Integer, 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;

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

//Java 8
PentFunction<String, Integer, Double, Boolean, String, 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;

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

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

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

除此之外,Java8并没有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);
}

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

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

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

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

//Java 8
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中使用,当看不到类型名时才有意义。

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)

从规则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<String>,而且它只有方法命名为get()。indexOf是一个属于String对象的实例方法。因此,必须重写这段代码,如下所示。

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

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

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

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

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

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

现在,可以看看Scala方法。

//Scala
val add = (a: Int) => (b:Int) => a + b
add(1)(2)
//Scala
val add = () => (a: Int,b: Int) => a + b
add2()(1,2)

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

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

(责编/仲浩)


90+位讲师,16大分论坛,Databricks公司联合创始人、Apache Spark首席架构师辛湜,Hadoop、HBase和Thrift项目的PMC成员和Committer、Kudu的发明人Todd Lipcon等海外专家将亲临2015中国大数据技术大会,票价折扣即将结束,预购从速


第九届中国大数据技术大会官网:http://www.csdn.net/article/2015-11-10/2826168

购票通道:http://bdtc2015.hadooper.cn/dct/page/1

Java 8 vs. Scala(一): Lambda表达式 Java 8 vs. Scala(一): Lambda表达式

本文为CSDN原创翻译,未经允许不得转载。


Java 8 vs. Scala(一): Lambda表达式

Java 8 vs. Scala(一): Lambda表达式
Java 8 vs. Scala(一): Lambda表达式


长按指纹即可关注哦!每天都会为你推荐有趣有用的资料!喜欢就分享给更多人吧!

以上是关于Java 8 vs. Scala: Lambda表达式的主要内容,如果未能解决你的问题,请参考以下文章

Lambda表达式用法大比较: Scala和Java 8

Lambda表达式用法大比较: Scala和Java 8

Lambda表达式

热点Scala 2.12将只支持Java 8

Java 8 并行排序与 Scala 并行排序

Java1.8新特性