你的位置:首页 > Java教程

[Java教程]Java 8 vs. Scala之Lambda表达式


Hussachai Puripunpinyo认为Java是一门静态的强类型语言,因此虽然在Java 8 中函数已经成了一等公民,可以作为函数的参数或者返回值来传递,但是它必须要有一个类型,那就是接口,而Lambda表达式就是实现了Functional接口的对象。虽然开发人员不需要为函数对象的创建而担心,因为编译器会做这些事情,但是Java并没有Scala那么出色的类型推理机制,在Java中声明Lambda表达式必须要指定目标类型。考虑到Java必须维持向后兼容性,这样做也是可以让人接受和理解的,事实上在兼容性方面Java已经做的足够好了,例如Thread.stop()在JDK 1.0中就已经有了,虽然被标记为“已废弃”数十年,但是现在依然存在,因而不应该因为其他语言有更好的语法就期望Java快速地改变自己的语法。

为了支持Lambda表达式Java引入了函数式接口,该接口只有一个抽象方法。@FunctionalInterface是一个注解,用来表明被注解的接口是一个函数式接口,该注解是可选的,只有需要对接口是否符合契约做检查的时候才需要使用。

在Java中,Lambda表达式必须要有一个类型,而且类型必须有且仅有一个抽象方法,而大部分已有的回调接口已经满足这一需求,因此用户不需要对它们做出任何改变就能够重用这些接口。例如:

//在Java 8之前Runnable 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<string, integer=""> parseInt = (String s) -> Integer.parseInt(s);

因为参数类型可以从Function对象的类型声明中推断出来,所以类型和小括号都可以省略:

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

对于需要两个参数的函数,Java 8提供了BiFunction:

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

对于需要3个及以上参数的接口,Java 8并没有提供相应的TriFunction、QuadFunction等定义,但是用户可以定义自己的TriFunction,如下:

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

在引入了之前定义好的接口之后就可以这样声明Lambda表达式:

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

对于语言的设计者为什么会止步于BiFunction,Hussachai Puripunpinyo认为TriFunction、QuadFunction等需要更多参数的接口需要太多的类型声明,接口的定义变得非常长,同时又怎么决定定义到哪一个才最合适呢,总不能一直定义到包含9个参数和一个返回值类型的EnnFunction吧!

以上示例显示参数越多,类型定义越冗长,甚至可能整整一行都是类型声明,那么必须要声明类型么?答案是在Java中必须如此,但是在Scala中就简单的多了。

Scala也是一门静态强类型的语言,但是它从诞生开始就是一门函数式语言,完美融合了面向对象范式和函数式语言范式。Scala中的Lambda表达式也有一个类型,但是语言的设计者采用了数字而不是拉丁语来命名,Scala为开发者提供了0到22个参数的接口定义(Function0、Function1、… Function22),如果需要更多的参数,那么或许是开发者在设计上就存在问题。在Scala中Function的类型是特性(trait),类似于Java中的抽象类。

Scala中的Runnable示例与Java中的实现方式不同:

//ScalaFuture(println{“This should be run in another thread”})//以上代码等同于//Java 8//assume that you have instantiated ExecutorService beforehand.Runnable r = () -> System.out.println(“This should be run in another thread”);executorService.submit(r);

在Scala中声明一个Lambda表达式不必像Java那样必须显式指定类型,而且方式也有很多:

//Java 8Function<string, integer=""> 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<integer, integer="" integer,=""> 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;

可以看到,Scala的语法更简洁,可读性更好,开发者不需要声明接口类型,通过参数列表中的类型就能看出对象的类型。

//Java 8PentFunction<string, integer,="" string="" string,="" boolean,="" double,="">  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;

对于上面这段代码,开发者一打眼就能看出i3是Double类型的,但是在Java 8中开发者必须要数一数才能看出来,如果要在Java 8中达到这种效果,那只有从格式上来做文章了:

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

但是这真是非常糟糕,开发者必须一次次地键入类型,另外,Java 8并没有定义PentFunction,你还必须自己定义:

//Java 8@FunctionalInterfaceinterface PentFunction<a, r="" c,="" b,="" e,="" d,=""> {  public R apply(A a, B b, C c, D d, E e);}

Hussachai Puripunpinyo认为Scala在函数式方面做的更好,一方面是因为Scala本身就是一门函数式语言,另一方面是因为Java语言的设计者在引入新东西的时候必须要考虑兼容性,因而有很多约束。但是即使如此,Java 8依然引入了一些非常酷的特性,例如方法引用,该特性就能够让Lambda表达式的声明更加简短:

//Java 8Function<string, integer=""> parseInt = s -> Integer.parseInt(s);//使用方法引用可以简写为://Java 8Function<string, integer=""> parseInt = Integer::parseInt;

在Java 8中,方法引用的构建规则有3种:

  1. (args) -> ClassName.staticMethod(args);
    可以重写为ClassName::staticMethod;
    Function<integer, string=""> intToStr = String::valueOf;
  2. (instance, args) -> instance.instanceMethod(args);
    可以重写为ClassName::instanceMethod;
    BiFunction<string, integer="" string,=""> indexOf = String::indexOf;
  3. (args) -> expression.instanceMethod(args);
    可以重写为expression::instanceMethod;
    Function<string, integer=""> indexOf = new String()::indexOf;

流API就大量使用了这种语法来简化代码的编写:

pets.stream().map(Pet::getName).collect(toList());// The signature of map() function can be derived as// Stream map(Function mapper)

前两天在一群里看见有人推荐一个app叫问啊,就可以发题答题那种的,感觉就跟uber滴滴打车似的,一般这种软件一上来就砸钱给红包啥的,哥之前刷过uber的单有经验!试验了几次应该可以刷,把注册红包和之前领的红包钱套现,目前我提了五十多,目测还能刷更多。ps,但是尽量要问技术相关的问题,不然容易被封。

  有技术的可以自己试,不会的可以q我QQ群290551701