文章大纲
引言
初识Lambda[ˈlæmdə]是在13年左右,是在另一门微软的语言C#中的,当时就觉得Lambda很简洁,可奇怪的是类似Scala这种JVM语言早已经支持Lambda了,而Java语言直到JDK 8才引入Lambda的支持,所以很多人可能一开始接触的时候不太了解和不太习惯Lambda的写法。
一、语法糖概述
语法糖(Syntactic sugar)也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指在原有计算机语言的基础上中添加某种语法且该语法对语言的原有功能没有影响,但是可以更方便地供程序员使用,通常使用语法糖能够增强代码的可读性和简洁性的,从而减少程序代码出错的机会。语法糖并非是某一门语言所特有的,事实上很多语言都支持语法糖,也并不意味着同样的功能使用语法糖就一定比普通写法更高效。虽然JDK 1.8之前也有语法糖的应用,但JVM并不是直接支持这些语法糖,这些语法糖在编译阶段就会被还原成简单的基础语法结构(具体是在com.sun.tools.javac.main.JavaCompiler 的源码中的 compile() 方法内调用 desugar() 方法)即解语法糖过程,典型的有以下的应用:
-
JDK 7 引入的switch语句支持 String 与枚举作为条件变量,原本Java中的switch语句只支持char、byte、short、int的(对于int类型,直接进行数值的比较;对于char类型则是比较其ascii码),而引入语法糖之后,就支持String和枚举类型,就是因为在编译成功后把原始的String条件变量,通过获取hashCode()转为int整形变量,然后再去比较。
-
自动装箱与拆箱,编译成功后装箱(Java自动将基本类型值转换成对应的对象)就是通过调用包装器对象的valueOf方法来获取对应的对象,而拆箱(将对象转换成对应的基本类型则是通过调用包装器对象的 xxxValue方法实现的。
-
可变参数在被使用的时候,编译成功后首先会创建一个数组,而数组的长度就是调用该方法是传递的实参的个数,再把参数值全部放到这个数组当中,最后把这个数组作为参数传递到被调用的方法中。
-
内部类,可理解为外部类的一个普通成员,编译成功后就会生成两个独立的.class文件,比如outer.java里面定义了一个内部类inner,desugar之后就会生成两个完全不同的.class文件了,分别是outer.class和outer$inner.class。所以内部类的名字完全可以和它的外部类名字相同。
-
数值字面量,在Java 7中,数值字面量,不管是整数还是浮点数,都允许在数字之间插入任意多个下划线,编译后成功后就是把_删除了。
以上仅仅是列出部分典型的语法糖应用。
二、Lambda表达式概述
Lambda表达式是语法糖应用的一种形式,可以把它简单理解为是一种可传递的匿名方法或匿名内部类的实现形式(但不宜认为它是匿名方法或匿名内部类的语法糖),Lambda表达式中没有方法(类)名称,由形参列表、方法主体、返回值(可省)三部分构成,整个Lambda表达式可以看成一个普通变量进行赋值操作和传参操作中,在JVM内部Lambda表达式会被编译为invokedynamic指令,每一个Lambda表达式的实现逻辑均会被封装为一个对应的静态私有方法,只要存在Lambda表达式调用,编译后便会生成一个内部类,所以Lambda中的this指针指向的是周围的类(定义该Lambda表达式时所处的类)。
1、Lambda基本语法
函数式接口的普通写法经过去掉修饰符、去掉方法名、去掉返回值类型(编译器自动推断)、去掉参数类型(编译器自动推断)后,保留参数列表、方法体和返回值,再通过Lambda(箭头) 操作符 ->就可将函数式接口转变为Lambda表达式:形参列表和方法体通过Lambda操作符连接起来,如下图所示:
除了以上这种Lambda操作符的表现形式,还有一种特殊(当方法体对应的操作已有实现的方法时)的表现形式——方法引用,其基本语法是使用引用操作符 :: 将方法名和对象实例或类名连接起来,主要有三类:
- 类名::静态方法名
- 类名::实例方法名
- 对象::实例方法名
方法引用中还有一种特殊的形式——构造器引用类名::new,总之请记住:Lambda表达式的简化对象是函数式接口,所以 Lambda 表达式都能隐式地赋值给其对应的函数式接口。
2、函数式接口
函数式接口(Functional Interface)中有且只有一个抽象方法,换言之有且只有一个需要被实现的抽象方法的接口叫做函数式接口。JDK中为了明确标识函数式接口引入了 @FunctionalInterface注解,同时也是为了避免在原函数式接口中增加新的方法声明导致其变成非函数式接口,如果你在使用了 @FunctionalInterface注解的接口里声明了多个方法则编译失败,也正是因为函数式接口这一显著特性使得Lambda中可以省略函数名(因为一个接口只对应一个函数)
3、Lambda表达式的结构特点
- 一个 Lambda 表达式可以有零个或多个形参。
- 形参的类型既可以明确声明,也可以省略不写。
- 所有形参均包含在圆括号内,形参之间用逗号连接。
- Lambda方法体中调用方法的参数列表与返回值类型与函数式接口中抽象方法的参数列表和返回值类型一致
- 空圆括号代表形参列表为空。
- 当只有一个参数且其类型可推导时,小括号()可省。
- Lambda 表达式的主体(方法体)可包含零条或多条语句。
- 如果 Lambda 表达式的主体只有一条语句,则花括号{}可省。匿名函数的返回类型与该主体表达式一致
- 如果 Lambda 表达式的主体包含一条以上语句,则表达式必须包含在花括号内形成代码块,匿名函数的返回类型与代码块的返回类型一致,若没有返回则为空。
三、Lambda表达式的简单应用
1、Runnable
2、Lambda表达式作为参数传递
3、方法引用
import java.io.PrintStream;
import java.util.Comparator;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
* @author : Crazy.Mo
*/
public class FuncReference {
public static void main(String[] args) {
Supplier<Double> supplier = new Supplier<Double>() {
@Override
public Double get() {
return Math.random();
}
};
//Lambda操作符形式
Supplier<Double> supplier2=()->Math.random();
System.out.println("supplier2"+supplier2.get());
//1、方法引用形式,类名::静态方法名
Supplier<Double> supplier3=Math::random;
System.out.println("supplier3"+supplier3.get());
Comparator<Integer> stringComparator = new Comparator<Integer>(){
@Override
public int compare(Integer s1, Integer s2) {
/**
* 你对一个函数式接口中的抽象方法重写时,若传递两个参数,其中一个参数是作为调用者,另一个作为实参
* 则可以使用方法引用来简写Lambda表达式
*/
return s1.compareTo(s2);
}
};
Comparator<Integer> stringComparator2=(s1,s2)->s1.compareTo(s2);
//2、方法引用,类名::实例方法名
Comparator<Integer> stringComparator3=Integer::compareTo;
stringComparator2.compare(66,88);
stringComparator3.compare(66,88);
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
PrintStream out = System.out;
out.println(s);
}
};
PrintStream out = System.out;
Consumer<String> consumer2=(s)->out.println(s);
//3、方法引用(没有参数),对象名::实例方法名
Consumer<String> consumer3=out::println;
consumer2.accept("Lambda形式");
consumer3.accept("对象::实例方法名形式");
}
}
4、构造器引用形式
构造方法与函数式接口相结合并自动与函数式接口中方法兼容,可以把构造器引用赋值给定义的方法,构造器参数列表要与接口中抽象方法的参数列表一致。
class MyClass {
String s;
public MyClass(String s) {
this.s = s;
}
}
/**
* 定义与构造方法参数一致的函数式接口
* @param <T>
* @param <R>
*/
@FunctionalInterface
public interface MyFuncInterface<T, R> {
R myNew(T t);
}
@Test
public void main2() {
//传统的方式
MyFuncInterface<String, MyClass> myFuncInterface1 = new MyFuncInterface<String, MyClass>() {
@Override
public MyClass myNew(String s) {
return new MyClass(s);
}
};
//Lamada方式---首先实现此方法
MyFuncInterface<String, MyClass> myFuncInterface2 = (s) -> new MyClass(s);
//构造器引用的方式,不管参数了列表了,简化方法体
MyFuncInterface<String, MyClass> myFuncInterface3 = MyClass::new;
//得到MyClass对象
MyClass myclass = myFuncInterface3.myNew("构造器引用形式");
System.out.println(myclass.s);
}