Java 8 vs. Scala(一): Lambda表达式(java面试题)

网友投稿 845 2022-09-11

本站部分文章、图片属于网络上可搜索到的公开信息,均用于学习和交流用途,不能代表睿象云的观点、立场或意见。我们接受网民的监督,如发现任何违法内容或侵犯了您的权益,请第一时间联系小编邮箱jiasou666@gmail.com 处理。

Java 8 vs. Scala(一): Lambda表达式(java面试题)

数年等待,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

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

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

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

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

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

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

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

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

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

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

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

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

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

//Java 8PentFunction sumOfFive = (i1, i2, i3, i4, i5) -> i1 + i2 + i3 + i4 + i5;

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

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

//ScalaFuture(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);

//Java 8Function parseInt = s -> Integer.parseInt(s);//Scalaval parseInt = (s: String) => s.toInt//orval parseInt:String => Int = s => s.toInt//orval parseInt:Function1[String, Int] = s => s.toInt

//Java 8PentFunction sumOfFive = (i1, i2, i3, i4, i5) -> i1 + i2 + i3 + i4 + i5;//Scalaval sumOfFive = (i1: Int, i2: Int, i3: Int, i4: Int, i5: Int) => i1 + i2 + i3 + i4 + i5;

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

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

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

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

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

//Java 8@FunctionalInterfaceinterface PentFunction { 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 8Function parseInt = s -> Integer.parseInt(s);

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

//Java 8Function parseInt = Integer::parseInt;

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

方法引用的构造规则

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

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

Function intToStr = String::valueOf;

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

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

BiFunction indexOf = String::indexOf;

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

FunctionindexOf = 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// Stream map(Function mapper)

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

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

Supplier str =String::new;

那么可以这样做吗?

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

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

//JavaFunction indexOf = ((Supplier)String::new).get()::indexOf;

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

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

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

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

//JavaSupplier> add = () -> (a, b) -> a + b;add.get().apply(2, 3);

现在,可以看看 Scala 方法。

//Scalaval add = (a: Int) => (b:Int) => a + badd(1)(2)//Scalaval add = () => (a: Int,b: Int) => a + badd2()(1,2)

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

上一篇:安全防护就像猫鼠游戏!最好的解决方案是啥?(从猫鼠游戏开始无防盗)
下一篇:论互联网合并趋势之不一样的「合并」(在互联网公司的合并重组)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~