Lambda
本文简单认识JDK8的重要新特性之一——Lambda表达式。 在JDK8之前,Java是不支持函数式编程的,所谓的函数编程,即可理解是将一个函数(也称为“行为”)作为一个参数进行传递。通常我们提及得更多的是面向对象编程,面向对象编程是对数据的抽象(各种各样的POJO类),而函数式编程则是对行为的抽象(将行为作为一个参数进行传递)。在JavaScript中这是很常见的一个语法特性,但在Java中将一个函数作为参数传递这却行不通,好在JDK8的出现打破了Java的这一限制。
通过创建一个线程来认识Lambda表达式
在JDK1.8之前,我们通常这样创建一个线程:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello innerclass!");
}
}).start();
这段代码中,我们创建了一个实现了Runnable
接口的匿名内部类对象,并将该对象作为参数传到Thread
类的构造方法中,从而创建了一个线程。
下面展示一下如何通过Lambda表达式来创建一个线程:
new Thread(() -> System.out.println("hello lambda!")).start();
在这行代码中,我们通过一个Lambda表达式代替了传统的匿名内部类对象,代码简洁了很多。其中() -> System.out.println("hello lambda!")
就是一个Lambda表达式。
从这个例子中可以看出,一个Lambda表达式包含以下三个部分:
-
最左边是一个
()
,表示该Lambda表达式的参数列表。 -
中间是Lambda操作符
->
(也叫箭头操作符)。 -
最右边是Lambda表达式中需要执行的功能函数体,即Lambda体。
Lambda体可以是一行代码,也可以是代码块(即一对花括号包住的多行代码)。
比较上面两种创建线程的方法,可以发现有一个共同点,即都是通过调用Thread
类的构造方法Thread(Runnable target)
来创建的Thread
对象。由此可以得到一个结论:() -> System.out.println("hello lambda!")
这个Lambda表达式本质上就是一个实现了Runnable
接口的匿名内部类对象。
所以我们也可以将一个Lambda表达式赋给一个函数式接口类型的引用:
Runnable runnable = () -> System.out.println("hello lambda!");
那么什么样的接口才能转换成Lambda呢?
能够接收Lambda表达式的参数类型,是一个只包含一个抽象方法的接口。换句话说,只要是一个方法,其有一个参数的类型是只包含一个抽象方法的接口,那么,就可以传入一个Lambda表达式作为该方法的这个参数。
自定义一个函数式接口,并使用Lambda表达式进行参数传递
首先,先定义一个函数式接口:
public interface FunctionInterface {
void function();
}
再定义一个方法,其参数类型为FunctionInterface
类型:
public void func(FunctionInterface functionInterface) {
functionInterface.function();
}
将Lambda表达式作为参数传入func(FunctionInterface functionInterface)
方法,进行调用:
func(()-> System.out.println("lambda 实现functionInterface"));
可以看到,只要是一个接口中只包含一个抽象方法,则可以使用Lambda表达式,这样的接口称之为“函数式接口”。
关于函数式接口
Java 8为函数式接口引入了一个新注解@FunctionalInterface
,主要用于编译级错误检查,加上该注解,编译器就会将该接口当成一个函数式接口,当你写的接口不符合函数式接口定义的时候,编译器会报错。
函数式接口是指有且只有一个抽象方法的接口,Object类中的public方法除外。换句话说就是,如果一个接口中除了重写的Object类中的public的抽象方法外,还有且仅有一个抽象方法,那么这个接口就是一个函数式接口。
什么叫Object类中的public方法除外
呢?
举个例子,我们来看一下JDK8中的Comparator接口的源码:
从上图的源码中可以看到,Comparator
接口中有两个抽象方法,但是它依然被@FunctionalInterface
注解,也就是说它是符合函数式接口的概念。这是什么原因呢?这是因为equals
方法是Object
类中的public
方法,在Comparator
接口中重写了这个方法。这就是Object类中的public方法除外
的意思。
Lambda表达式的几种形式
Java中的Lambda表达式主要有以下几种形式:
-
无参数无返回值
-
有参数无返回值
-
有参数有返回值
下面通过代码详细讲解:
-
无参数,无返回值
前面的两个例子都是无参数无返回值的例子,格式就是
() -> System.out.println("hello lambda!")
。 -
有参数,无返回值
首先定义函数式接口,其抽象函数中声明了两个参数:
public interface FunctionInterface { void function(int x, int y); }
再定义一个方法来调用函数接口中定义的方法:
public void func(FunctionInterface functionInterface) { int x = 1; int y = 2; functionInterface.function(x, y); }
通过Lambda表达式进行调用:
// 显示声明参数的数据类型 func((int x, int y) -> System.out.println(x + y)); // 也可以省略参数的数据类型 func((x, y) -> System.out.println(x + y));
从上面的例子可以看出,在使用Lambda表达式时,可以省略参数类型,编译器会根据上下文自动推断数据类型,当然有些情况下(比如定义函数式接口时的参数类型是一个泛型),编译器也不能推断出参数的数据类型,这时就会编译报错。
当只有一个参数时,左边的小括号也是可以省略的,比如
x -> System.out.println(x)
。 -
有参数,有返回值
先定义一个有参数有返回值的函数式接口:
public interface FunctionInterface { boolean function(int x, int y); }
使用Lambda表达式调用:
FunctionInterface fi = (x, y) -> { boolean b = x > y; return b; }; System.out.println(fi.function(1, 2));
就是这么简单,就是这么自然!!!