Java8实践指南(一) Lambda表达式基础详解

本文通过代码实例详细解释了Lambda表达式的基本原理,主要包括以下内容

  • 基本语法
  • 函数的概念
  • 函数式接口
  • 方法引用
  • Lambda表达式的优点
  • 总结

本文面向有Java编程基础,但是对 Lambda表达式不熟悉的程序员。


基本语法

让我们先来看一个简单的例子

(x, y) -> x + y

上面这个看起来不像java语句的表达式就是一个简单的lambda表达式。

从Java8开始,可以用lambda表达式来代入到函数接口的变量中去。函数接口是只有一个需要实现的方法的接口,后续会详细解释。lambda表达式基本语法如下:

( 参数 ) -> { 処理 }

其中->称为lambda运算符,它的左边是参数列表,右边是一系列的处理以及返回值等。

参数部分的示例

() -> { 处理 }  // 参数0个
(str) -> { 处理 ]  // 参数1个
str -> { 处理 }  // 参数1个的时候可以省略()
(str, n) -> { 处理 }  // 参数2个
(String str, int n) -> { 处理 }  //  也可以写上具体的参数类型

处理部分的示例

( 参数 ) -> System.out.println(str)  //只有一句的时候可以省略return
( 参数 ) -> {                                  
    System.out.println(str);   // 复数个语句的时候用{}括起
    return n;                  // 需要返回值的时候使用return
}

lambda表达式由参数和返回值构成,听起来有点像方法。实际上lambda表达式在本质上与方法是有点类似的。上面例子里的lambda表达式表示的是这样的方法:它的参数是整数型的x,y,然后它的处理是将x与y相加的和作为返回值返回,用方法来类比的话,有点类似以下的方法

class SomeClass{
    public int sum(intx,inty){
        return x+y;
    }
}

为什么说有点类似方法呢,因为lambda表达式严格来说表示的一个函数的对象。

函数的概念

在前一节中,我们引出一个函数的概念。在Java语言中,一个接受一些参数,执行一些操作并返回某种值的语句块称为方法,而在其他一些编程语言中,这样的语句块称为函数。从Java8开始,Java语言也引入了函数的概念,虽然函数≈方法,但是在细节上有一些细微的差别。

Java语言中的方法是类的成员,在类的定义中编写方法的定义。然后,类被实例化成为对象,方法仅存在于对象之中。打个比方,假设类的实例是个西瓜,那方法就像是西瓜里的种子,被西瓜包围。

而函数略有不同,因为函数本身就是一个对象。方法被对象包围,而函数却直接暴露在外。假如方法是被西瓜包围的种子,那么函数就像是把种子从西瓜中取出来,加工而成的西瓜子,本身是独立存在,并暴露在外的。函数对象的特点是没有字段(状态),只有一个表示行为的一段处理。

Java是典型的面向对象编程语言,在编程语言史上还有一类更古老的函数式编程语言。函数式编程语言将函数视为第一等公民,换句话说,将函数视为对象本身,可以像普通的值一样来对待。那么,“可以像普通的值一样来对待“究竟是什么意思呢,用Java来比喻的话,比如下面这两条语句:

int i=1
String str="ABC"

看似相似,其实略有不同。第一个语句里的1是真正的值,而第二条语句里的"ABC"实际上是String类的实例,这个实例可以像普通的值一样对待。函数也与这个类似。

在函数式编程语言中,可以做如下的操作:

  • 可以将函数分配给变量
  • 可以将函数传递给函数的参数
  • 可以返回函数作为返回值

其中满足2和3的函数称为"高阶函数"(Higher-order Function)。
而Java语言中传统的方法,是无法做到以上三点的,这就是方法与函数最重要的区别。

Java8将函数式编程语言的优点融入到了面向对象编程语言里去,听起来好像是语言规范做了重大的变化,但是事实并非如此。Java8只是将函数式编程语言的精髓在概念和语法上进行了吸收,而编译器会在后台将源代码进行自动转换。

让我们用传统的Java来说明一下这一点。Java5添加了"自动装箱和自动拆箱"功能,也就是说基本数据类型和相应的包装类类型会自动地相互转换。它看起来像这样:

int x=10;
Integer y=x;
int z=y;

在概念和语法上,这看起来确实是int型的整数值10自动转换为了Integer类型的对象y。但实际上,在编译的时候,编译器会将源代码进行自动的变换,最终变为如下所示的代码:

int x=10;
Integer y=new Integer(x);
int z=y.intValue();

也就是说,为了让程序员更便捷的编码,Java编译器一直在幕后努力工作。而Java8里的函数式编程也是如此。

函数式接口

Java Lambda表达式在概念和语法上来讲是一个函数的对象,在本质上来讲的话,它是一个实现了函数式接口的匿名类对象的简洁写法。

"函数式接口"(Function Interface)这个名词听起来好像很难理解,其实它是一个非常简单的概念。Java8将只包含单个抽象方法声明的接口称为函数式接口,它只是一个人为的规定,仅此而已。

与此同时,Java8添加了一个@FunctionalInterface注解。这个注解写在接口定义的前面时,就表明这个接口是一个函数式接口,编译器在编译时会检查该接口定义是否符合函数式接口的要求(只包含单个抽象方法声明)。基本上你应该很少会用到这个注解来定义自己的函数式接口,因为Java8提供了大量的泛用的函数式接口,足够我们使用了。

在Java8之前Java API中已经有好多接口属于函数式接口了。例如,只有一个抽象方法"void run()"的Runnable接口,以及同样类似的,只有抽象方法"int compare(T o1,T o2)()"的Comparator接口。

这里的重点是"单个抽象方法",没有或有2个以上抽象方法的接口不是函数式接口。那么接口里能够声明的明明只有抽象方法,这里为什么会一再强调抽象方法呢?在Java8之前,接口上确实只能声明抽象方法。但是,Java8的接口规范发生了较大的规范变更。Java8的接口现在可以定义具有static关键字的已实现的静态方法,以及具有default关键字的已实现的默认方法。所以,这里所强调的抽象方法,是为了与这些已实现的静态方法与默认方法作区别。另外,函数式接口所持有的这种"单个抽象方法",可以用SAM(Single Abstract Method)来简称。

Java8定义了43种函数式接口,它们位于java.util.function包里。比如IntBinaryOperator,它的定义如下:

你可能感兴趣的:(Java8实践指南(一) Lambda表达式基础详解)