开源中国

我们不支持 IE 10 及以下版本浏览器

It appears you’re using an unsupported browser

为了获得更好的浏览体验,我们强烈建议您使用较新版本的 Chrome、 Firefox、 Safari 等,或者升级到最新版本的IE浏览器。 如果您使用的是 IE 11 或以上版本,请关闭“兼容性视图”。
Java 8 vs. Scala:Part I - 技术翻译 - 开源中国社区

Java 8 vs. Scala:Part I 【已翻译100%】

标签: Java Scala
oschina 推荐于 2年前 (共 7 段, 翻译完成于 11-16) 评论 64
收藏  
57
推荐标签: Java Scala 待读

比较 Java 8 和 Scala 在使用 Stream API 时的表达方式和性能的差异

经过漫长的等待,终于等到了有着高阶函数的 Java 8。我迷恋 Java,但是我必须承认和现在一些其它的语言相比 Java 的语法确实是十分的冗余。现在使用 lambda 表达式,我就可以编写实用且可读性的代码(有时,比传统的方式可读性更强

虽然 Java 8 在 2014 年 3 月就发布了,但是我最近才有机会使用它。自从我知道 Scala 后,就一直想比较一下 Java 8 和 Scala 的表达方式和性能。主要是在使用 Stream API 的时候,并且我将为你展示怎么使用 Stream API 去操作集合。

因为这篇文章很长所以我把它文件分成了 3 个部分。

1.lambda 表达式

2. Stream API vs Scala collection API

3. Trust no one, bench everything. (来自 sbt-jmh)


我是彩笔
 翻译得不错哦!

首先我们来看下 Java 8 中的 lambda 表达式。我不知道即使它的一部分是可替换的他们仍然把这个叫做 lambda 表达式。我能用一些声明替换它,我也可以说 Java 8 也支持拉姆达声明。我们讨论的就是编程语言把函数当成一等公民。即使不给函数传参它也能编译成功因为函数被当作对象看待。Java 是一个静态类型和强类型的语言。所以,函数必须是有个类型,因此它也是一个接口。也就是说,lambda 函数就是继承函数接口的一个类。你不用去创建这个函数的类,编译器会帮你实现。不幸的是,Java 没有像 Scala 那样完美的接口。如果你想声明一个 lambda 表达式,你必须给出输出结果的数据类型。因为 Java 是在后台编译它并且做的很好所以还是可以接受的。例如,Thread.stop() 在JDK 1.0 的时候就被移除了,并被标志为不可用的长达十年,但是现在它又回来了。所以你不能仅仅因为 语言 xyz 的语法更好就期望 Java 去完全去改变它的语法。

所以,Java 8 的设计就非常酷了。它是一个函数接口!函数接口就是只有一个抽象方法的接口。大多数的回调函数已经满足这样要求,所以我们不做任何修改就可以直接使用这些接口。@FunctionalInterface 是用来表明带注释接口是一个函数接口的注释。这个注释是可选的,而且除非你有一个检查的需求否则不需要对接口做任何特殊的处理。

我是彩笔
 翻译得不错哦!

请谨记 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);

如果一个函数有两个参数呢?

不用担心 Java 8 中有 BiFunction

//Java 8

BiFunction<Integer, Integer, Integer> multiplier = 

  (i1, i2) -> i1 * i2; //you can’t omit parenthesis here!

你能想象一个有三个参数的函数吗?

TriFunction?不过因为语言的设计者不是拉丁语的专家,否则我们就有TriFunction,QuadFunction,PentFunction 等等这些了。然而我们可以定义我们自己的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 中,一个 lambda 表达式最多可以有 22 个参数,而且对每个函数 Scala 都有一个对应的类型。函数的类型在 Scala 中是一个 Trait。Trait 像 Java 中抽象类但是可以当做一个混合类型使用。

如果你想要使用超过 22 个参数,那个我必须说你的设计肯定是有错误的。因为你必须要考虑你用过的所有类型。

在这里我就不过多的描述 lambda 表达式的细节了,你可以很容易从其他地方得到关于它的很多信息。你可以在 这里 和这里找到。

让我们来看一看Scala的一些其它内容。Scala 也是像 Java 一样是一个静态类型和强类型的语言,但是它的本质是一个函数语言。所以它是面向对象和函数编程混合产物。

我是彩笔
 翻译得不错哦!

我不能给你展示一个 Runnerable 的 Scala 例子(如同我上面给 java 做的),因为它是来自 java 不同方法途径。Scala 有它自己的解决问题的方式。所以,我会展示用 Scala 的方式替代。

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

和下面的 Java 8 代码等效

//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 那样。

如果你想描述一个 lambda 表达式,可以直接描述而不需要像在 Java 里那样描述 explicit。

//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 更短些 因为你不需要要声明接口类型,并且 Integer 类型在 Scala 中是 Int(Integer 的丰富版本有个更短名字)。更短不意味着总是更好。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,但是在 java8 中,你不得不去数它来找到 i3 是什么类型。除非你有一个超人的眼睛。

你可能也会说,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;

非常恐怖了吧,不是吗?因为你不得不一遍又一遍的重复类型。

此外, 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);
}
liuqiangchengdu
 翻译得不错哦!

我不会说 Scala 更好,但是有些地方 Scala 确实更好。Scala 也有很多比 Java 糟糕的地方。但是我不会告诉你哪一个更好,因为那只是我的观点。我用 Scala 和 Java 8 比较是因为 Scala 是一个函数语言而 Java 8 有一些函数的组件。所以我必须找一个函数语言去和比较 Java 8 的这些函数组件。

因为 Scala 是运行在 JVM 上的所以我选择 Scala 来和 Java 8 比较。你可能会看到当使用函数的时候 Scala 有着简单且好的语法和方法。因为 Java 的设计者在设计 Java 的时候考虑更多的是在不打破一些旧的东西的基础上设计一些新的东西。所以尽管 Java 在使用拉姆达表达式时有一些限制,但是它也引入一些比较酷的东西。例如,利用方法引用的特性通过重现现有的方法使得编写拉姆达表达式更简洁。等一下,使得拉姆达表达式更简洁?

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

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 函数 只需要一个参数,目标类型是一个双重函数表明了他需要两个参数。实际上,这个奇特指出常常用在 Stream API 并且当你不看类型名的时候才有意义。

pets.stream().map(Pet::getName).collect(toList());
// map()函数签名能够推导出下面的形式
// <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;

不幸的是,不能!它不会被编译并且编译器提示“表达式的目标类型必须是一个函数式接口”。这个很有趣,因为目标类型实际上就是函数式接口。你能告诉我为什么吗?说真的,你能吗,反正我不确定原因。

下一篇:Java 8 vs Scala — Part II Streams API

liuqiangchengdu
 翻译得不错哦!
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们
评论(64)
Ctrl/CMD+Enter

Iambda 已经破坏了 Java 的整个设计哲学。甲骨文现在不过是把 Java 走 PHP 路线,尽可能的往里塞,到最后就是一个说着十国语言充满了京腔味的韩国人
java的设计哲学是什么 @jQer
1楼的不懂java就不要瞎JB YY
翻译有点问题:I don’t know why they call this feature lambda expressions when the expression part is substitutable. I can use statements instead of an expression ...,作者原意是这个特性不应该叫lambda expressions,而应该叫lambda statements

引用来自“fkbt点cc”的评论

1楼的不懂java就不要瞎JB YY
同意,Church是图灵的老师,图灵机也不过就是lambda演算的一个具体实现,所有计算机语言的通用真理被质疑,只能说明自己内功还不够

引用来自“jQer”的评论

Iambda 已经破坏了 Java 的整个设计哲学。甲骨文现在不过是把 Java 走 PHP 路线,尽可能的往里塞,到最后就是一个说着十国语言充满了京腔味的韩国人
你为什么不说甲骨文再让java走lisp路线,高端大气上档次

引用来自“jQer”的评论

Iambda 已经破坏了 Java 的整个设计哲学。甲骨文现在不过是把 Java 走 PHP 路线,尽可能的往里塞,到最后就是一个说着十国语言充满了京腔味的韩国人
java设计哲学是啥?干了好多年的java开发依旧不懂得求指教

引用来自“jQer”的评论

Iambda 已经破坏了 Java 的整个设计哲学。甲骨文现在不过是把 Java 走 PHP 路线,尽可能的往里塞,到最后就是一个说着十国语言充满了京腔味的韩国人

引用来自“ixiaohei”的评论

java设计哲学是啥?干了好多年的java开发依旧不懂得求指教
同问啊
这lambda表达式着实晦涩难懂

引用来自“jQer”的评论

Iambda 已经破坏了 Java 的整个设计哲学。甲骨文现在不过是把 Java 走 PHP 路线,尽可能的往里塞,到最后就是一个说着十国语言充满了京腔味的韩国人

引用来自“ixiaohei”的评论

java设计哲学是啥?干了好多年的java开发依旧不懂得求指教
完全面向对象,而Lambda表达式是函数式编程的特征。Java那么高贵,怎么能像PHP那样有用的东西都加进去呢?哈哈。
这篇文章让我记起了自己竟然曾经学过Scala
"现在使用 lambda 表达式,我就可以编写实用且可读性的代码(有时,比传统的方式可读性更强)。" ----- 这句话不敢苟同,怎么感觉lamdba表达式让我更头晕?难道因为我并没有学过PHP?或者一个java程序员需要学习PHP才能证明Java使用lamdba表达式更好?这是什么逻辑?

引用来自“jQer”的评论

Iambda 已经破坏了 Java 的整个设计哲学。甲骨文现在不过是把 Java 走 PHP 路线,尽可能的往里塞,到最后就是一个说着十国语言充满了京腔味的韩国人
只知道PHP 不知道js 么

引用来自“jQer”的评论

Iambda 已经破坏了 Java 的整个设计哲学。甲骨文现在不过是把 Java 走 PHP 路线,尽可能的往里塞,到最后就是一个说着十国语言充满了京腔味的韩国人

引用来自“ixiaohei”的评论

java设计哲学是啥?干了好多年的java开发依旧不懂得求指教
摘自码农,James Gosling在2014年JavaOne大会上谈到了Lambda以及Java的早期版本中没有出现的一些设计,他说:如果我没有找到完成一件事的正确方法,那我就什么都不做。这句话表达了一种缓慢而保守的演进设计思想,要想理解Java是什么,就必须要明白这点。很多人觉得Java老了,编程语言需要改变,但是他们没有搞清楚的是,真正变化的是他们自己。他们在能力上有了发展,他们想看得更远更深,而语言反映出了这一点。并不是语言需要改变,而是提出这个观点的程序员自身发生了变化。Java从过去到未来都是一种设计保守的语言。这也是Java的一大优势。
Java8 有几个特性我是真没搞懂
1.接口提供方法 -- 这是要填补旧坑的妥协么?
2.lambda -- 都写了这么多年"繁琐"的代码了 现在搞这出是什么意思?要不全转Scala得了 别写Java了
龟壳真是, 模块化什么时候出 ?

引用来自“jQer”的评论

Iambda 已经破坏了 Java 的整个设计哲学。甲骨文现在不过是把 Java 走 PHP 路线,尽可能的往里塞,到最后就是一个说着十国语言充满了京腔味的韩国人

引用来自“ixiaohei”的评论

java设计哲学是啥?干了好多年的java开发依旧不懂得求指教

引用来自“eechen”的评论

完全面向对象,而Lambda表达式是函数式编程的特征。Java那么高贵,怎么能像PHP那样有用的东西都加进去呢?哈哈。
你把函数式编程和PHP扯在一起也是真看得起PHP

引用来自“fkbt点cc”的评论

1楼的不懂java就不要瞎JB YY
就你这种东西,哥下边随遍拉个打杂的都够秒你十条街

引用来自“jQer”的评论

Iambda 已经破坏了 Java 的整个设计哲学。甲骨文现在不过是把 Java 走 PHP 路线,尽可能的往里塞,到最后就是一个说着十国语言充满了京腔味的韩国人

引用来自“攻伤菊菊长”的评论

你为什么不说甲骨文再让java走lisp路线,高端大气上档次
扯淡,你写过 lisp 吗,call/cc 在 java 永远也无法实现

引用来自“jQer”的评论

Iambda 已经破坏了 Java 的整个设计哲学。甲骨文现在不过是把 Java 走 PHP 路线,尽可能的往里塞,到最后就是一个说着十国语言充满了京腔味的韩国人

引用来自“webit”的评论

只知道PHP 不知道js 么
js 是函数式架构,跟 c++ java 这种在设计之初就把自己堵死在纯面向对象的傻叉思想是两回事。在纯面向对象中,根本不存函数这种东西,更不存在函数是值这种设定
顶部