Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的代码。最近学到Disruptor时,使用其publishEvent方法中的lambda表达式,发现对于这块理解的还不是很透彻,在此重新梳理一下。
在 Java 语言中,可以为变量赋予一个值。
把一个代码块赋给一变量,在 Java 8 之前,是做不到的。但是 Java 8 问世之后,利用 Lambda 特性,就可以做到了。
我们来看一个例子,这个例子中我们看到removeIf的参数是一个Predicate类型的对象,里面有一个未实现的方法test(T t)。按照java 8之前我们要new 一个继承Predicate的对象,然后传给removeIf。
而这里ele -> ele.equals(“b”)就相当于创建了一个Predicate对象,并实现了test方法。变量filter被赋值了一个代码块ele -> ele.equals(“b”)。“这块代码”,或者说 “这个被赋给一个变量的函数”,就是一个 Lambda 表达式。
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.removeIf(ele -> ele.equals("b")); //使用了lambda表达式
// removeIf源码
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) { //调用接口中的test方法
each.remove();
removed = true;
}
}
return removed;
}
// Predicate 接口
public interface Predicate<T> {
// 未实现接口
boolean test(T t);
// default方法
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
// default方法
default Predicate<T> negate() {
return (t) -> !test(t);
}
// default方法
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
最直观的作用就是使得代码变得异常简洁。
虽然使用 Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现。Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法。
jdk 8 中有另一个新特性:default, 被 default 修饰的方法会有默认实现,不是必须被实现的方法(从上面的代码中也能看到),所以不影响 Lambda 表达式的使用。
@FunctionalInterface 标记在接口上,“函数式接口”是指仅仅只包含一个抽象方法的接口。
(parameters) -> expression
或
(parameters) ->{ statements; }
语法形式为 () -> {},其中 () 用来描述参数列表,{} 用来描述方法体,-> 为 lambda 运算符 ,读作(goes to)。
可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其 2 倍的值
x -> 2 * x
// 3. 接受 2 个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收 2 个 int 型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回 void)
(String s) -> System.out.print(s)
// 6. 大括号需要指明返回结果
(x, y) -> {
x = y;
return x – y
}
下面的案例是lambda最复杂的一种形式了。其他可以根据上面的案例,自行试验比如无参无返,有参无返等等。
/**
*有返回值,有多个参数*
*/
@FunctionalInterface
interface ReturnMultiParam{
int method(int a,int b);
}
public static void main(String[] args) {
/***
*有返回值,有多个参数*
*/
ReturnMultiParam returnMultiParam = (int a ,int b)->{
System.out.print("ReturnMultiParam ");
return a+b;
};
System.out.println(returnMultiParam.method(10,20));
}
/***
*有返回值,有多个参数*
*/
ReturnMultiParam returnMultiParam = (int a ,int b)->{
System.out.print("ReturnMultiParam ");
return a+b;
};
/***
* 简化版
*/
ReturnMultiParam returnMultiParam = (a ,b)->a+b;
System.out.println(returnMultiParam.method(10,20));
}
有时候我们不是必须使用 Lambda 的函数体定义实现,我们可以利用 lambda 表达式指向一个已经被实现的方法。
方法归属者::方法名 静态方法的归属者为类名,普通方法归属者为对象。
/*** 有返回值,有一个参数 */
@FunctionalInterface interface ReturnOneParam{
int method(int a);
}
public class Test {
ReturnMultiParam returnMultiParam = (a ,b)->a+b;
System.out.println(returnMultiParam.method(10,20));
}
/*** 要求:
* 1,参数的个数以及类型需要与函数接口中的抽象方法一致。
* 2,返回值类型要与函数接口中的抽象方法的返回值类型一致。
* @param a * @return
*/
public static int doubleNum(int a){
return 2*a;
}
public int addTwo(int a){
return a+2;
}
}
public class Test2 {
public static void main(String[] args) {
ReturnOneParam returnOneParam = Test::doubleNum;
int value = returnOneParam.method(10);
System.out.println(value);
Test test = new Test();
ReturnOneParam returnOneParam1 = test::addTwo;
int value2 = returnOneParam1.method(10);
System.out.println(value2);
}
}
public class Test3 {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+ "开始");
new Thread(()->{
for(int i=0;i<20;i++){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
},"Lambda Thread ").start();
System.out.println(Thread.*currentThread*().getName()+"结束");
}
}
我们可以调用集合的 public void forEach(Consumer super E> action) 方法,通过 lambda 表达式的方式遍历集合中的元素。Consumer 接口是 jdk 为我们提供的一个函数式接口。
我们通过 public boolean removeIf(Predicate super E> filter)方法来删除集合中的某个元素,Predicate 也是 jdk 为我们提供的一个函数式接口。
传入比较器重写compare 方法的比较器对象,现在我们还可以使用lambda表达式来简化代码。
以下是代码示例
public class Test4 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
// 遍历打印
list.forEach(System.out::println);
// 排序
list.sort((o1,o2)->o1.compareTo(o2));
list.forEach(System.out::println);
// 删除某种元素
list.removeIf(ele->ele.equals("b"));
list.forEach(System.out::println);
}
}
闭包的本质就是代码片断。所以闭包可以理解成一个代码片断的引用。在 Java 中匿名内部类也是闭包的一种实现方式。
在闭包中访问外部的变量时,外部变量必须是 final 类型,虚拟机会帮我们加上 final 修饰关键字。
public class Test7 {
public static void main(String[] args) {
int num =10;
NoReturnNoParam noReturnNoParam =
()->System.out.println(num);
noReturnNoParam.method();
}
}