Lambda表达式
1. Lambda表达式简介
Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。
比如之前我们创建一个Runnable多线程程序需要这样写:
//使用Runnable接口创建线程
Thread th1 = new Thread( new Runnable(){
public void run(){
System.out.println("aaa");
}
});
//启动线程
th1.start();
那么使用Lambda表达式后代码会变成这样:
//lambda表达式
Thread th2 = new Thread( () -> System.out.println("bbb") );
//启动线程
th2.start();
当然还可以更简单
//更简单的写法
new Thread( () -> System.out.println("ccc") ).start();
其中 ( ) 用来描述参数列表,{ } 用来描述方法体,-> 为 lambda运算符 ,读作(goes to)。从这个例子中我们可以看出Lambda表达式的一些特点:
- JDK 提供了大量的内置函数式接口供我们使用,使得Lambda表达式的运用更加方便、高效。
- Lambda表达式,也可以称为闭包,它是推动Java8发布的最重要新特性。
- Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
- 使用Lambda表达式可以使代码变的更加简洁紧凑。
2. Lambda表达式语法
Lambda表达式的语法为:
( 参数列表 ) -> {方法体}
( ) : 用于描述参数列表;
{ } : 用于描述方法体;
-> : Lambda运算符,可以叫做箭头符号,或者 goes to
-
对接口的要求
虽然使用 Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现。Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法 。
不过jdk 8 中有另一个新特性:default, 被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用
-
@FunctionalInterface
修饰函数式接口的,要求接口中的抽象方法只有一个。 这个注解往往会和 lambda 表达式一起出现。比如我们上个例子用到的Runnable接口源码中就有这个注解。
@FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }
这里我们根据接口方法参数,有无返回值,罗列6种只包含单个方法的接口,分别使用Lambda表达式来实现。
- 无参数,无返回值
interface Source01{
//无参数 无返回值
void test();
}
public static void main(String[] args) {
Source01 s01 = ( )->{
System.out.println("无参数 无返回值");
};
s01.test();
}//main()
- 有参数,无返回值
interface Source02{
//有参数 无返回值
void test(int a);
}
public static void main(String[] args) {
Source02 s02 = (int a)->{
System.out.println("有参数 "+a+" 无返回值");
};
s02.test(10);
}//main()
- 有多个参数,无返回值
interface Source03{
//有多个参数 无返回值
void test(int a,String b);
}
public static void main(String[] args) {
Source03 s03 = (int a,String b)->{
System.out.println("有多个参数 "+a+","+b+" 无返回值");
};
s03.test(10,"张三");
}//main()
- 无参数,有返回值
interface Source04{
//无参数 有返回值
int test( );
}
public static void main(String[] args) {
Source04 s04 = ( )->{
System.out.println("无参数 有返回值");
return a;
};
int ret = s04.test();
System.out.println( ret );
}//main()
- 有参数,有返回值
interface Source05{
//有参数 有返回值
int test( int a );
}
public static void main(String[] args) {
Source05 s05 = (int a)->{
System.out.println("有参数 "+a+" 有返回值");
return a*100 ;
};
int ret=s05.test(88);
System.out.println( ret );
}//main()
- 有多个参数,有返回值
interface Source06{
//有多个参数 有返回值
int test( int a , int b );
}
public static void main(String[] args) {
Source06 s06 = (int a,int b)->{
System.out.println("有多个参数 "+a+","+b+" 有返回值");
return a*b ;
};
int ret=s06.test(8,8);
System.out.println( ret );
}//main()
3. Lambda表达式精简语法
Lambda表达式可以将代码进一步简化,写出更加优雅的代码,规则如下:
- 参数类型可以省略
- 只有一个参数时,( ) 可以省略
- 只有一条语句时,{ } 可以省略
- 只有一条return语句时,{ } 和 retrun 可以省略
这样我们将上一节6个接口的实现重新编写为:
//只有一条语句
Source01 s01 = ( ) -> System.out.println("无参数 无返回值");
s01.test();
//省略参数类型 + 只有一个参数 + 只有一条语句
Source02 s02 = a -> System.out.println("有参数 "+a+" 无返回值");
s02.test(10);
//省略参数类型 + 只有一条语句
Source03 s03 = ( a,b ) -> System.out.println("有参数 "+a+","+b+" 无返回值");
s03.test(10,"李四");
//只有一条return语句
Source04 s04 = ( )-> 88;
int ret04 = s04.test();
System.out.println( ret04 );
//省略参数类型 + 只有一个参数 + 只有一条return语句
Source05 s05 = a -> a *100;
int ret05 = s05.test(88);
System.out.println( ret05 );
//省略参数类型 + 只有一条return语句
Source06 s06 = (a, b)-> a*b ;
int ret06 = s06.test(8,8);
System.out.println( ret06 );
4. Lambda表达式常用引用
4.1 方法引用
我们在编写代码时,有时会出现多个Lambda表达式实现函数式接口代码一致的情况,为了便于维护,我们可以封装成通用方法,这时就可以用方法引用实现,语法为:
对象 : : 方法
如果是static方法,语法为:
类名 : : 方法
比如我们在开发中有这样的场景:
interface Source07{
int test( int a );
}
public static void main(String[] args) {
Source07 s07 = a -> a + 10;
System.out.println( s07.test(10) );
//...若干行其他代码后
//又出现对Source07的实现且业务功能一致
Source07 s08 = a -> a + 10;
System.out.println( s07.test(20) );
}//main()
此时就可以将实现封装起来,如果封装到类中我们可以编写如下方法:
public int sourceTest(int a){
return a + 10;
}
然后再次使用相同实现时,可以这样使用:
Source07 s09 = lmd03::sourceTest;
System.out.println( s07.test(30) );
完整代码如下:
class Lambda03 {
interface Source07{
int test( int a );
}
public static void main(String[] args) {
Source07 s07 = a -> a + 10;
System.out.println( s07.test(10) );
//...若干行其他代码后
//又出现对Source07的实现且业务功能一致
Source07 s08 = a -> a + 10;
System.out.println( s07.test(20) );
//使用方法引用的代码
Lambda03 lmd03 = new Lambda03();
Source07 s09 = lmd03::sourceTest;
System.out.println( s07.test(30) );
Source07 s10 = lmd03::sourceTest;
System.out.println( s07.test(40) );
}//main()
//封装通用接口实现
public int sourceTest(int a){
return a + 10;
}
}
当然我们也可以将通用方法封装成 static,使用时用类名 : : 方法的方式:
class Lambda03 {
interface Source07{
int test( int a );
}
public static void main(String[] args) {
Source07 s07 = a -> a + 10;
System.out.println( s07.test(10) );
//...若干行其他代码后
//又出现对Source07的实现且业务功能一致
Source07 s08 = a -> a + 10;
System.out.println( s07.test(20) );
//使用静态方法引用的代码
Source07 s09 = Lambda03::sourceTest;
System.out.println( s07.test(30) );
Source07 s10 = Lambda03::sourceTest;
System.out.println( s07.test(40) );
}//main()
//封装static通用接口实现
public static int sourceTest(int a){
return a + 10;
}
}
运行结果:
20
30
40
50
4.2 构造方法引用
这种方式我们需要声明接口,该接口作为对象的生成器,通过 对象名::new 的方式来实例化对象,然后调用方法返回对象,语法为:
对象类名 : : new
比如先定义一个员工 Employee 类,实现两个构造方法:
class Employee {
private String empName;
private int empLevel;
public Employee() {
super();
System.out.println("创建 无参 Employee");
}
public Employee(String empName, int empLevel) {
super();
this.empName = empName;
this.empLevel = empLevel;
System.out.println("创建 有参数的 Employee");
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public int getEmpLevel() {
return empLevel;
}
public void setEmpLevel(int empLevel) {
this.empLevel = empLevel;
}
@Override
public String toString(){
return this.empName +" " + this.empLevel;
}
}
然后我们创建两个对象生成器接口:
//无参构造器引用接口
interface EmployeeCrt{
Employee getEmployee();
}
//有参构造器引用接口
interface EmployeeCrtMult{
Employee getEmployee(String name, int level);
}
使用时,我们分别用传统的Lambda表达式和构造器引用来创建3个对象:
//之前的方式
EmployeeCrt crt1 = ()-> new Employee();
System.out.println( crt1.getEmployee() );
//使用封装好的构造器引用(无参的)
EmployeeCrt crt2 = Employee::new; //对象类名::new
System.out.println( crt2.getEmployee() ); //调用生成器方法返回对象
//多个参数的构造器引用
EmployeeCrtMult crt3 = Employee::new; //对象类名::new
System.out.println( crt3.getEmployee("刘经理",5) );//调用生成器方法返回对象
运行结果:
创建 无参 Employee
null 0
创建 无参 Employee
null 0
创建 有参数的 Employee
刘经理 5
5. FunctionalInterface注解
函数式接口注解,表示这是一个只有一个抽象方法的接口。也叫做SAM接口,即 Single Abstract Method interfaces,特点有:
- 接口有且仅有一个抽象方法
- 允许定义静态方法
- 允许定义默认方法
- 允许java.lang.Object中的public方法
- 对于符合
函数式接口
定义的接口,不加该注解不影响使用,加上能够更好地让编译器进行检查;如果不符合函数式接口
定义的接口,加上@FunctionalInterface会报错。
这里我们给出一个例子:
@FunctionalInterface
interface fctItfcTest{
//一个抽象方法
int method1();
//java.lang.Object中的public方法
public boolean equals(Object obj);
//默认方法
public default void defMethod(){
}
//静态方法
public static void sticMethod(){
}
}
下面这个例子加上@FunctionalInterface是错误的:
//去掉注解 是正确的接口但不是 函数式接口
//加上注解 会出现编译错误
interface fctItOtherTest{
void add( );
void delete( );
}
6. 系统内置的函数式接口
Java8的推出,是以Lambda这个重要特性一起推出的,因此在java.util.function包下系统也内置了一系列的函数式接口,大家可以自行查看。
7. Lambda表达式应用举例
对象排序:
ArrayList<Employee> empList = new ArrayList<Employee>();
empList.add( new Employee("w经理",6) );
empList.add( new Employee("x经理",5) );
empList.add( new Employee("l经理",1) );
empList.add( new Employee("z经理",4) );
empList.add( new Employee("c经理",2) );
/* 传统匿名内部类写法 ArrayList.sort(Comparator<? super E> c)
empList.sort( new Comparator<Employee>(){
public int compare(Employee e1,Employee e2){
return e1.getEmpLevel() - e2.getEmpLevel();
}
});
*/
//Lambda写法 ArrayList.sort(Comparator<? super E> c)
empList.sort( (e1,e2)-> e1.getEmpLevel() - e2.getEmpLevel() );
/* 传统的forEach写法
for(Employee e : empList){
System.out.println( e );
}
*/
//lambda ArrayList.forEach(Consumer<? super E> action)
empList.forEach( e -> System.out.println( "lambda foreach " + e ));