【编者按】本文作者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 Runnable r = () -> System.out.println(“This should be run in another thread”);
0
//Java 8 Runnable r = () -> System.out.println(“This should be run in another thread”);
1
所以,在Scala中的确有多种办法来声明类型。让编译器来执行。那么PentFunction呢?
//Java 8 Runnable r = () -> System.out.println(“This should be run in another thread”);
2
//Java 8 Runnable r = () -> System.out.println(“This should be run in another thread”);
3
Scala更短,因为不需要声明接口类型,而整数类型在Scala中是int。短不总意味着更好。Scala的方法更好,不是因为短,而是因为更具可读性。类型的上下文在参数列表中,可以很快找出参数类型。如果还不确定,可以再参考以下代码。
//Java 8 Runnable r = () -> System.out.println(“This should be run in another thread”);
4
//Java 8 Runnable r = () -> System.out.println(“This should be run in another thread”);
5
在Scala中,可以很明确地说出i3类型是Double型,但在Java 8中,还需要算算它是什么类型。你可能争辩说Java也可以,但出现这样的状况:
//Java 8 Runnable r = () -> System.out.println(“This should be run in another thread”);
6
你必须一遍又一遍的重复下去。
除此之外,Java8并没有PentFunction,需要自己定义。
//Java 8 Runnable r = () -> System.out.println(“This should be run in another thread”);
7
是不是意味着Scala就更好呢?在某些方面的确是。但也有很多地方Scala不如Java。所以很难说到底哪种更好,我之所以对两者进行比较,是因为Scala是一种函数语言,而Java 8支持一些函数特点,所以得找函数语言来比较。由于Scala可以运行在JVM上,用它来对比再好不过。可能你会在使用函数时,Scala有更简洁的语法和方法,这是因为它本来就是函数语言,而Java的设计者在不破坏之前的基础上拓展设计,显然会有更多限制。
尽管Java在语法上与lambda表达式相比有一定局限性,但Java8 也引进了一些很酷的功能。例如,利用方法引用的特性通过重用现有方法使得编写lambda表达式更简洁。更简洁吗???
//Java 8 Runnable r = () -> System.out.println(“This should be run in another thread”);
8
可以使用方法引用来重写函数,如下所示
//Java 8 Runnable r = () -> System.out.println(“This should be run in another thread”);
9
还可以通过实例方法来使用方法引用。之后会在第二部分的Stream API中指出这种方法的可用性。
方法引用的构造规则
1.(args) -> ClassName.staticMethod(args);
可以像这样重写ClassName::staticMethod;
//Java 8 Function<String, Integer> parseInt = (String s) -> Integer.parseInt(s);
0
2.(instance, args) -> instance.instanceMethod(args);
可以像这样重写 ClassName::instanceMethod;
//Java 8 Function<String, Integer> parseInt = (String s) -> Integer.parseInt(s);
1
3.(args) -> expression.instanceMethod(args);
可以像这样重写 expression::instanceMethod;
//Java 8 Function<String, Integer> parseInt = (String s) -> Integer.parseInt(s);
2
你有没有注意到规则2有点奇怪?有点混乱?尽管indexOf函数只需要1个参数,但BiFunction的目标类型是需要2个参数。其实,这种用法通常在Stream API中使用,当看不到类型名时才有意义。
//Java 8 Function<String, Integer> parseInt = (String s) -> Integer.parseInt(s);
3
从规则3中,你可能会好奇能否用 lambda 表达式替换 new String()?
你可以用这种方法构造一个对象
//Java 8 Function<String, Integer> parseInt = (String s) -> Integer.parseInt(s);
4
那么可以这样做吗?
//Java 8 Function<String, Integer> parseInt = (String s) -> Integer.parseInt(s);
5
不能。它不能编译,编译器会提示“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 8 Function<String, Integer> parseInt = (String s) -> Integer.parseInt(s);
6
Java 8 是否支持currying (partial function)?
的确可行,但你不能使用方法引用。你可以认为是一个partial函数,但是它返回的是函数而不是结果。接着将要介绍使用currying的简单实例,但这个例子也可能行不通。在传递到函数之前,我们通常进行参数处理。但无论如何,先看看如何利用lambda表达式实现partial 函数。假设你需要利用currying实现两个整数相加的函数。
//Java 8 Function<String, Integer> parseInt = (String s) -> Integer.parseInt(s);
7
该函数可以同时采用两个参数。
//Java 8 Function<String, Integer> parseInt = (String s) -> Integer.parseInt(s);
8
现在,可以看看Scala方法。
//Java 8 Function<String, Integer> parseInt = (String s) -> Integer.parseInt(s);
9
//Java 8 Function<String, Integer> parseInt = s -> Integer.parseInt(s);
0
因为类型引用和语法糖,Scala的方法比Java更简短。在Scala中,你不需要在Function trait上调用apply 方法,编译器会即时地将()转换为apply 方法。
原文链接: https://dzone.com/articles/java-8-λe-vs-scalapart-i (责编/仲浩)
推荐阅读:Java 8 vs. Scala(二):Stream vs. Collection
90+位讲师,16大分论坛,Databricks公司联合创始人、Apache Spark首席架构师辛湜,Hadoop、HBase和Thrift项目的PMC成员和Committer、Kudu的发明人Todd Lipcon等海外专家将亲临2015中国大数据技术大会,票价折扣即将结束,预购从速。
本网页所有文字内容由 imapbox邮箱云存储,邮箱网盘, iurlBox网页地址收藏管理器 下载并得到。
ImapBox 邮箱网盘 工具地址: https://www.imapbox.com/download/ImapBox.5.5.1_Build20141205_CHS_Bit32.exe
PC6下载站地址:PC6下载站分流下载
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox 网页视频 工具地址: https://www.imapbox.com/download/ImovieBox4.7.0_Build20141115_CHS.exe
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算