Lambda 表达式,也称为闭包,是 Java 8 中最大和最令人期待的语言改变。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理,函数式开发者非常熟悉这些概念。
很多JVM平台上的语言(Groovy、Scala等)从诞生之日就支持 Lambda 表达式,但是 Java 开发者没有选择,只能使用匿名内部类代替Lambda表达式。
使用 Lambda 表达式可以使代码变的更加简洁紧凑。让 Java 也能支持简单的函数式编程🐖
表达式语法:
(parameters) -> expression
或
(parameters) ->{ statements; }
Lambda 编程风格,可以总结为四类:
过去给方法传动态参数的唯一方法是使用内部类。比如我们想实现一个集合的排序比较方法:
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(521);
arrayList.add(1314);
// 使用内部类的方式实现排序
arrayList.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
System.out.println(arrayList); // [1314, 521]
使用Lambda表达式简化内部类的操作:
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(521);
arrayList.add(1314);
// Lambda
arrayList.sort((Integer o1, Integer o2) -> o2 - o1);
System.out.println(arrayList); // [1314, 521]
再比如,在Java8之前,我们使用内部类的方式开启一个新的线程:
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是传统的多线程操作!");
}
}).start();
}
使用Lambda表达式,一切都变得简洁起来:
new Thread(() -> System.out.println("我是Lambda表达式开启的线程")).start();
那它对接口有没有要求呢?我们发现这些匿名内部类只重写了接口的一个方法,当然也只有一个方法须要重写。这就是我们上文提到的函数式接口,也就是说只要方法的参数是函数式接口都可以用 Lambda 表达式。
@FunctionalInterface
public interface Comparator<T>{}
@FunctionalInterface
public interface Runnable{}
传统foreach方法遍历集合:
List<String> strings = Arrays.asList("1", "2", "3");
for (String string : strings) {
System.out.println(string);
}
使用Lambda表达式遍历:
List<String> strings = Arrays.asList("1", "2", "3");
strings.forEach((s) -> System.out.println(s));
// map的遍历方法
map.forEach((k,v)->System.out.println(v));
第一种场景:无参,无返回值类型
/**
* Lambda表达式快捷开启线程
* 第一种:无参,无返回值类型
*/
@Test
public void test1() {
Runnable runnable = () -> System.out.println("我爱中国!");
runnable.run();
}
第二种场景:需要参数,但是没有返回值
/**
* 需要参数,但是没有返回值
*/
@Test
public void test2() {
Consumer<String> con = (String s) -> {
System.out.println("s=" + s);
};
con.accept("dahe");
}
场景三:类型推断
/**
* 类型推断
* 数据类型可以进行省略操作,可以由编译器进行类型推断
*/
@Test
public void test3() {
Consumer<String> con = (s) -> {
System.out.println("s=" + s);
};
con.accept("dahe");
}
场景四:参数列表只有一个参数,可以省略括号
@Test
public void test4() {
Consumer<String> con = s -> {
System.out.println("s=" + s);
};
con.accept("dahe");
}
场景五:参数列表有多个参数,并且可以有返回值
/**
* 参数列表有多个参数,并且可以有返回值
*/
@Test
public void test5() {
Comparator<Integer> comparator = (o1, o2) -> {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
};
System.out.println(comparator.compare(521, 1314));
}
定义:也称 SAM 接口,函数接口指的是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口,这样的接口可以隐式转换为 Lambda 表达式。
但是在实践中,函数式接口非常脆弱,只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface
一些没有@FunctionalInterface 注解,但是只要符合函数式接口的定义就是函数式接口,与是否有@FunctionalInterface注解无关,注解只是在编译时起到强制规范定义的作用。其在 Lambda 表达式中有广泛的应用。
Lambda表达式的本质:作为函数式接口的实例🐏
以下是一个函数式接口的案例:
@FunctionalInterface
public interface GreetingService {
void sayMessage(String message);
}
Java 8 允许使用 :: 关键字来传递方法或者构造函数引用,无论如何,表达式返回的类型必须是 functional-interface。
例子:
之前我们使用Lambda表达式遍历一个集合:
List<String> strings = Arrays.asList("1", "2", "3");
strings.forEach((s) -> System.out.println(s));
使用方法引用,它还可以更加的简洁:
List<String> strings = Arrays.asList("1", "2", "3");
strings.forEach(System.out::println);
之前使用Lambda表达式实现的Consumer:
Consumer<String> con = s -> {
System.out.println("s=" + s);
};
con.accept("dahe");
使用方法引用还可以更加的简洁:
Consumer<String> con = System.out::println;
con.accept("dahe");
综合实例:
public class LambdaClassSuper {
LambdaInterface sf(){
return null;
}
}
public class LambdaClass extends LambdaClassSuper {
public static LambdaInterface staticF() {
return null;
}
public LambdaInterface f() {
return null;
}
void show() {
// 1.调用静态函数,返回类型必须是functional-interface
LambdaInterface t = LambdaClass::staticF;
// 2.实例方法调用
LambdaClass lambdaClass = new LambdaClass();
LambdaInterface lambdaInterface = lambdaClass::f;
// 3.超类上的方法调用
LambdaInterface superf = super::sf;
// 4. 构造方法调用
LambdaInterface tt = LambdaClassSuper::new;
}
}