Lambda表达式和Function 接口
一、什么是Lambda表达式?
Lambda表达式,也可称为闭包,是JDK8的新特性。Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中),可以使代码变的更加简洁紧凑。Lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。
名字起源是以前还没有计算机时,逻辑学家Alonzo Church想要形式化的表示能有效计算的数学函数,使用了希腊字母lambda(λ )来标记参数,从那以后,带参数变量的表达式就被称为lambda表达式。
lambda表达式本质是一个匿名函数,比如以下函数
public int add(int x, int y) {
return x + y;
}
可以转换为:
(int x, int y) -> x + y;
总结:
- Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
- Lambda 允许把函数作为一个方法的参数。
- 使用 Lambda 表达式可以使代码变的更加简洁紧凑。
- Lambda 表达式本质是一个匿名函数,是对匿名函数的简写形式。
二、为什么要引入Lambda表达式?
Lambda表达式可以让我们写出更简洁、更灵活的代码,能够让我们编程更加高效。
作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
比如当我们使用最基本的代码去创建一个子线程,代码如下:
package cn.imyjs.java;
/**
* @author YJS
* @ClassName TestLamdba
* @Description TODO
* @webSite www.imyjs.cn
* @date 2023/3/11 17:32
*/
public class TestLambda {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("Hello~");
}
}
为了使这段代码变得更加简洁,可以使用匿名内部类重构一下,代码如下:
public class TestLambda {
public static void main(String[] args) {
new Thread(new Runnable() {
// 把一个重写的run()方法传入了一个构造函数中。
@Override
public void run() {
System.out.println("Hello~");
}
}).start();
}
}
而上面的这段代码,不是最简单的,还可以进一步简化,也就是使用Lambda表达式来简化。
public class TestLambda {
public static void main(String[] args) {
new Thread(() -> {System.out.println("Hello~");}).start();
}
}
根据Lambda表达式简化的规则,终极简化代码如下:
public class TestLambda {
public static void main(String[] args) {
new Thread(() -> System.out.println("Hello~")).start();
}
}
通过上面一段基础代码的显示,相信你已经知道了为什么需要引入Lambda表达式了。
三、Lambda表达式的语法
JDK1.8之后引入的一种语法,他的写法是使用一个->
符号,箭头将Lambda表达式分为左右两部分,左边写的是实现的这个接口中的抽象方法中的形参列表,右边就是对抽象方法的处理;
([Lambda参数列表,即形参列表]) -> {Lambda体,即方法体}
拷贝小括号,写死右箭头,落地大括号,大括号中写上业务逻辑
以下是lambda表达式的重要特征:
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。
Lambda 表达式的简单例子:
// 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)
总结:
- 一个参数,小括号可省略
- 无参数或大于一个参数,小括号不可省略
- 能确定参数类型,可以省略类型关键字
- 方法体只有一句代码,可以省略大括号和return以及分号;
原则:可推导可省略
四、Lambda表达式的分类
因为Lambda表达式的核心就是实现这个接口中抽象方法中的形参列表 -> 抽象方法的处理
,因此根据形参列表与返回值的不同,Lambda表达式的具体写法也不相同;
有参无返回值
自定义接口:
package cn.imyjs.java.type1;
@FunctionalInterface
public interface MyInterface {
void show(int a, int b);
}
基础写法:
package cn.imyjs.java.type1;
public class Demo {
public static void main(String[] args) {
MyInterface myInterface = new MyInterface(){
@Override
public void show(int a, int b) {
System.out.println(a + b);
}
};
myInterface.show(10, 20); // 30
}
}
Lambda 表达式简写:
@Test
public void testLambda(){
// 简写1:方法名可以自己推断出来
MyInterface myInterface = (int a, int b) -> {
System.out.println(a + b);
};
myInterface.show(10, 20);
// 简写2:可以省略形参列表中的形参类型
MyInterface myInterface2 = (a, b) -> {
System.out.println(a + b);
};
myInterface2.show(10, 20);
// 简写3:如果抽象方法中只有一行代码,可以省略方法体的大括号,当然,如果不止一行,就不能省略
MyInterface myInterface3 = (a, b) -> System.out.println(a + b);
myInterface3.show(10, 20);
}
示例:
@Test
public void testForEach(){
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.forEach(t -> System.out.println(t));
// 方法引用更简单
list.forEach(System.out::println);
}
// forEach() 功能等同与增强型for循环 这个方法来自于Iterable接口,Collection接口继承了这个接口,List又继承了
// Collection接口,而ArrayList是List的实现类;forEach函数,指明该函数需要传入一个函数,而且是有参数没有返回值的函数,
// 而Consumer接口中正好有且仅有一个这样的有参无返回值的抽象方法。
- 可以省略方法名,IDEA会帮你自动检测方法名;
- 可以省略方法中的形参类型;
- 如果对抽象方法的实现逻辑只有一行,可以省略方法体的大括号,当然如果不止一行,就不能省略了;
有参有返回值
自定义接口
package cn.imyjs.java.type2;
public interface MyInterface {
int add(double a, double b);
}
基础写法:
package cn.imyjs.java.type2;
public class Demo {
public static void main(String[] args) {
MyInterface myInterface = new MyInterface() {
@Override
public double add(double a, double b) {
return a + b;
}
};
System.out.println(myInterface.add(2.0, 6.0));
}
}
Lambda 表达式简写:
@Test
public void testLambda(){
// 简写一
MyInterface myInterface = (double a, double b) -> {
return a + b;
};
System.out.println(myInterface.add(2.0, 6.0));
// 简写二
MyInterface myInterface2 = (a, b) -> {
return a + b;
};
System.out.println(myInterface2.add(2.0, 6.0));
// 简写3:这个有返回值的方法,不能直接去掉大括号,还需要去掉return关键字
MyInterface myInterface3 = (a, b) -> a + b;
System.out.println(myInterface3.add(2.0, 6.0));
}
示例:
@Test
public void test(){
// Collator是一个抽象类,但是提供了获取该类实例的方法getInstance()
Collator collator = Collator.getInstance();
TreeSet<Student> students =
new TreeSet<>((s1, s2) ->collator.compare(s1.getName(), s2.getName()));
students.add(new Student("小米", 80.0));
students.add(new Student("小红", 88.0));
students.add(new Student("小妹", 92.0));
students.forEach(System.out::println);
}
- 有返回值的方法,如果要去掉大括号,还需要去掉return关键字;
无参无返回值
自定义接口:
package cn.imyjs.java.type3;
public interface MyInterface {
void print();
}
基础写法:
package cn.imyjs.java.type3;
public class Demo {
public static void main(String[] args) {
MyInterface myInterface = new MyInterface() {
@Override
public void print() {
System.out.println("Hello");
}
};
myInterface.print();
}
}
Lambda 表达式简写:
@Test
public void testLambda(){
MyInterface myInterface = () -> System.out.println("Hello!");
myInterface.print();
}
示例:
public static void main(String[] args) {
new Thread(() -> System.out.println("Hello~"));
}
无参有返回值
自定义接口:
package cn.imyjs.java.type4;
public interface MyInterface {
String getName();
}
基础写法:
package cn.imyjs.java.type4;
public class Demo {
public static void main(String[] args) {
MyInterface myInterface = new MyInterface(){
@Override
public String getName() {
try {
Class<?> clazz = Class.forName("cn.imyjs.java.type4.Demo");
return clazz.getSimpleName();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
};
System.out.println(myInterface.getName()); // Demo
}
}
Lambda 表达式简写:
@Test
public void testLambda(){
MyInterface myInterface = ()->{
try {
Class<?> clazz = Class.forName("cn.imyjs.java.type4.Demo");
return clazz.getSimpleName();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
};
System.out.println(myInterface.getName());
}
示例:
@Test
public void test(){
//该方法将会不断的打印100以内的正整数。
Random random = new Random();
Stream<Integer> stream = Stream.generate(() ->random.nextInt(100));
stream.forEach(t -> System.out.println(t));
// Stream.generate()方法创建无限流,该方法要求传入一个无参有返回值的方法。
}
使用 Lambda 表达式需要注意以下两点:
- Lambda 表达式主要用来定义行内执行的方法类型接口(例如,一个简单方法接口)。
- Lambda 表达式免去了使用匿名方法的麻烦,并且给予 Java 简单但是强大的函数化的编程能力。
补充:变量作用域
Lambda
表达式只能引用标记了 final
的外层局部变量,这就是说不能在 Lambda内部修改定义在域外的局部变量,否则会编译错误。
public class Java8Tester {
final static String salutation = "Hello! ";
public static void main(String args[]){
GreetingService greetService1 = message ->
System.out.println(salutation + message);
greetService1.sayMessage("Runoob");
// Hello! Runoob
}
interface GreetingService {
void sayMessage(String message);
}
}
我们也可以直接在 lambda 表达式中访问外层的局部变量:
public class Java8Tester {
public static void main(String args[]) {
final int num = 1;
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2); // 输出结果为 3
}
public interface Converter<T1, T2> {
void convert(int i);
}
}
lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
int num = 1;
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2);
num = 5;
//报错信息:Local variable num defined in an enclosing scope must be final or effectively final
在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
String first = "";
Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length());
//编译会出错
Lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
五、什么是函数式接口?
即SAM(Single Abstract Method )接口,有且只有一个抽象方法的接口(可以有默认方法或者是静态方法和从Object继承来的方法,但是抽象方法有且只能有一个)。 JDK1.8之后,添加@FunctionalInterface表示这个接口是是一个函数式接口,因为有了@functionalInterface标记,也称这样的接口为Mark(标记)类型的接口。举例子:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
其实我们的Lambda表达式就是对函数式接口的一种简写方式,所以只有是函数式接口,我们才能用Lambda表达式;再换句话说,Lambda表达式需要函数式接口的支持,那函数式接口我们可以自己定义,当然JDK1.8也给我们提供了一些现成的函数式接口;
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
函数式接口可以被隐式转换为 Lambda 表达式。
可以通过 Lambda 表达式来创建该接口的对象,我们可以在任意函数式接口上使用@FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口;
@FunctionalInterface注解写在接口声明上面,如果报错,就不是函数式接口;
六、函数式接口有什么作用?
可以通过 Lambda 表达式来创建该接口的对象,我们可以在任意函数式接口上使用@FunctionalInterface
注解,这样做可以检查它是否是一个函数式接口;
@FunctionalInterface
注解写在接口声明上面,如果报错,就不是函数式接口;
上文中提到的例子:
new Thread(() -> System.out.println("hello")).start();
编译器会认为Thread()中传入的是一个Runnable的对象,而我们利用IDEA的智能感知,鼠标指向“->”或“()”的时候,会发现这是一个Runnable类型,实际上编译器会自动将Lambda表达式赋值给函数式接口,在本例中就是Runnable接口。本例中Lambda表达式将打印方法传递给了Runnable接口中的run()方法,从而形成真正的方法体。
而且,参数与返回值是一一对应的,即如果函数式接口中的抽象方法是有返回值,有参数的,那么要求Lambda表达式也是有返回值,有参数的。
七、自定义一个函数式接口
package cn.imyjs.java.type1;
@FunctionalInterface
public interface MyInterface {
void show(int a, int b);
}
八、四大函数式接口
- 消费型接口:Consumer< T> void accept(T t)有参数,无返回值的抽象方法;
- 供给型接口:Supplier < T> T get() 无参有返回值的抽象方法;
- 断定型接口: Predicate< T> boolean test(T t):有参,但是返回值类型是固定的boolean
- 函数型接口: Function< T,R> R apply(T t)有参有返回值的抽象方法;
除了这四个之外,在java.util.function包下还有很多函数式接口可供使用。
九、函数式接口
JDK 1.8 之前已有的函数式接口:
java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener
JDK 1.8 新增加的函数接口:
java.util.function
java.util.function 它包含了很多类,用来支持 Java的 函数式编程,该包中的函数式接口有:
序号 | 接口 & 描述 |
---|---|
1 | BiConsumer<T,U>代表了一个接受两个输入参数的操作,并且不返回任何结果 |
2 | BiFunction<T,U,R>代表了一个接受两个输入参数的方法,并且返回一个结果 |
3 | BinaryOperator |
4 | BiPredicate<T,U>代表了一个两个参数的boolean值方法 |
5 | BooleanSupplier代表了boolean值结果的提供方 |
6 | Consumer |
7 | DoubleBinaryOperator代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。 |
8 | DoubleConsumer代表一个接受double值参数的操作,并且不返回结果。 |
9 | DoubleFunction |
10 | DoublePredicate代表一个拥有double值参数的boolean值方法 |
11 | DoubleSupplier代表一个double值结构的提供方 |
12 | DoubleToIntFunction接受一个double类型输入,返回一个int类型结果。 |
13 | DoubleToLongFunction接受一个double类型输入,返回一个long类型结果 |
14 | DoubleUnaryOperator接受一个参数同为类型double,返回值类型也为double 。 |
15 | Function<T,R>接受一个输入参数,返回一个结果。 |
16 | IntBinaryOperator接受两个参数同为类型int,返回值类型也为int 。 |
17 | IntConsumer接受一个int类型的输入参数,无返回值 。 |
18 | IntFunction |
19 | IntPredicate:接受一个int输入参数,返回一个布尔值的结果。 |
20 | IntSupplier无参数,返回一个int类型结果。 |
21 | IntToDoubleFunction接受一个int类型输入,返回一个double类型结果 。 |
22 | IntToLongFunction接受一个int类型输入,返回一个long类型结果。 |
23 | IntUnaryOperator接受一个参数同为类型int,返回值类型也为int 。 |
24 | LongBinaryOperator接受两个参数同为类型long,返回值类型也为long。 |
25 | LongConsumer接受一个long类型的输入参数,无返回值。 |
26 | LongFunction |
27 | LongPredicateR接受一个long输入参数,返回一个布尔值类型结果。 |
28 | LongSupplier无参数,返回一个结果long类型的值。 |
29 | LongToDoubleFunction接受一个long类型输入,返回一个double类型结果。 |
30 | LongToIntFunction接受一个long类型输入,返回一个int类型结果。 |
31 | LongUnaryOperator接受一个参数同为类型long,返回值类型也为long。 |
32 | ObjDoubleConsumer |
33 | ObjIntConsumer |
34 | ObjLongConsumer |
35 | Predicate |
36 | Supplier |
37 | ToDoubleBiFunction<T,U>接受两个输入参数,返回一个double类型结果 |
38 | ToDoubleFunction |
39 | ToIntBiFunction<T,U>接受两个输入参数,返回一个int类型结果。 |
40 | ToIntFunction |
41 | ToLongBiFunction<T,U>接受两个输入参数,返回一个long类型结果。 |
42 | ToLongFunction |
43 | UnaryOperator |