JDK8新特性01:Lambda表达式与函数式接口
Lambda表达式
为什么需要Lambda表达式
在Java8之前,我们无法将函数作为参数传递给一个方法,也无法声明返回一个函数的方法.这与Javascript等函数式语言大为不同.
在很多时候,我们想要传递的是行为,而非数据,这就需要Lambda表达式了,下面以两个Lambda表达式典型应用场景为例,演示Lambda表达式的使用.
Lambda表达式使用场景1:代替匿名内部类
在GUI编程中大量使用匿名内部类,他们用来定义事件回调的行为,这是一种典型的传递行为而非传递数据的情况,因此可以使用Lambda表达式.
-
在Java8之前,要定义事件的回调函数,必须向事件传递一个匿名内部类,在该匿名内部类中定义事件的回调行为.
public static void main(String[] args) { JFrame jFrame = new JFrame("My JFrame"); JButton jButton = new JButton("My JButton"); jButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("Button Pressed"); System.out.println("some other procedure"); } }); jFrame.add(jButton); jFrame.setVisible(true); }
-
在Java8之后,可以使用Lambda表达式定义接口中需要实现的方法,用以替代匿名内部类
public static void main(String[] args) { JFrame jFrame = new JFrame("My JFrame"); JButton jButton = new JButton("My JButton"); jButton.addActionListener((ActionEvent event) -> { System.out.println("Button Pressed"); System.out.println("some other procedure"); }); jFrame.add(jButton); jFrame.setVisible(true); }
Lambda表达式使用场景2:集合操作
使用Java8中Collection
类新增的forEach()
方法,配合Lambda表达式,可以极大方便集合的遍历操作
-
在Java5之前,只能使用for循环来遍历集合:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8); for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); }
-
Java5引入了增强for循环,本质上是调用了迭代器进行迭代:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8); for (Integer i : list) { System.out.println(list.get(i)); }
-
使用Java8中
Collection
类新增的forEach()
方法遍历集合更为简洁:list.forEach(new Consumer<Integer>() { @Override public void accept(Integer i) { System.out.println(i); } });
forEach()
方法接收一个Consumer<T>
对象,其accept()
方法指定了遍历集合时对元素进行的行为,当然,可以用Lambda表达式代替上面的匿名内部类.List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8); list.forEach((Integer i) -> { System.out.println(i); });
上面三种遍历集合的方法都属于外部迭代
,使用JDK8新增的流Stream
对集合进行内部迭代
的效率更高.
Lambda表达式的语法
Java中的Lambda表达式语法如下:
(argument) -> {body}
其中argument
部分为函数参数,规定如下:
- 一个Lambda表达式可以有零个或多个参数.所有的参数必须包含在圆括号内,参数之间用逗号间隔,例如:
(a, b)
或(int a, int b)
或(String a, float b)
. - 参数的类型既可以显式声明,也可以根据上下文来自动推断,例如:
(int i)
与(i)
的效果相同,为保证可读性,可以显式声明类型.当只有一个参数,且参数类型可推导时,圆括号()
可省略,例如a -> retuan a*a
- 空圆括号代表参数集为空,例如:
() -> 42
.
其中body
部分为表达式的主体,规定如下:
- 表达式的主体可以包含零条或多条语句
- 如果表达式的主体只有一条语句,花括号
{}
和return
关键字都可以省略,匿名函数的返回值即为该主体表达式计算结果.(expression lambda
) - 如果Lambda表达式的主体包含一条以上语句,则表达式必须包含在
{}
代码块中,匿名函数的返回值必须由return
语句显式指定,若没有return
语句则返回值为空.(statement lambda
)
函数式接口
上边例子中遍历集合的forEach()
方法接收一个Consumer<T>
接口的实现类,观察Consumer
接口的源代码,发现其只有一个抽象方法accept()
,该方法定义了对遍历得到元素的行为.同时我们注意到,Consumer
接口被加以@FunctionalInterface
注解,该注解标识该接口为函数式接口.
函数式接口:有且只有一个抽象方法的接口
阅读@FunctionalInterface
注解的文档内容如下:
Conceptually, a functional interface has exactly one abstract method. Since
default methods
have an implementation, they are not abstract. If an interface declares an abstract method overriding one of the public methods ofjava.lang.Object
, that also does not count toward the interface’s abstract method count since any implementation of the interface will have an implementation fromjava.lang.Object
or elsewhere.
函数式接口是指那些有且只有一个精确的抽象方法的接口.default
方法不被认为是抽象方法;重新声明java.lang.Object
类中的方法为抽象方法也并不被认为是该类的抽象方法,因为这些方法总是会在Object
类中或其他地方被实现.
Note that instances of functional interfaces can be created with lambda expressions, method references, or constructor references.
函数式接口的实例可以通过:Lambda表达式,方法引用或构造方法引用被创建.
总结成一句话,即:
从Java8开始,很多符合函数式接口定义的接口(如:Runnable
,Comparator
等)开始被声明为函数式接口.
Lambda表达式的数据类型:函数式接口的实现类
在javascript等函数式编程语言中,函数作为’一等公民’,是可以由专门的函数类型变量的,然而对于Java来说,并不存在函数类型,所以Lambda表达式实际上就是函数式接口的实现类对象.
下面测试程序证明了这一点
@FunctionalInterface // 一个函数式接口,只有一个精确的抽象方法
interface MyFunctionalInterface {
// 一个自定义的精确抽象方法
void myAbstractMethod();
// 一个覆盖了Object对象的抽象方法
String toString();
}
public class Test {
public static void main(String[] args) {
// 使用Lambda表达式定义该函数式接口的一个实例
MyFunctionalInterface aLambdaExpression = () -> {
System.out.println("hello");
};
System.out.println(myFunctionalInterface.getClass());
System.out.println(myFunctionalInterface.getClass().getSuperclass());
System.out.println(myFunctionalInterface.getClass().getInterfaces()[0]);
}
}
程序输出如下,可以证明该Lambda表达式实际上是函数式接口myFunctionalInterface
的一个实现类
class Test$$Lambda$14/0x0000000801204840
class java.lang.Object
interface MyFunctionalInterface