Java 8 vs. Scala: Lambda表达式 Posted 2021-04-25 CSDN云计算 tags: 篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 本文为CSDN原创翻译,未经允许不得转载。 长按指纹即可关注哦!每天都会为你推荐有趣有用的资料!喜欢就分享给更多人吧! 以上是关于Java 8 vs. Scala: Lambda表达式的主要内容,如果未能解决你的问题,请参考以下文章 Lambda表达式用法大比较: Scala和Java 8 Lambda表达式用法大比较: Scala和Java 8 Lambda表达式 热点Scala 2.12将只支持Java 8 Java 8 并行排序与 Scala 并行排序 Java1.8新特性