Lambda 是一个匿名函数,我们可以把Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
注:示例中有些使用了函数式接口,可参考下文
Java8中引入了一个新的操作符 “->” 该操作符称为箭头操作符或 Lambda 操作符箭头操作符将 Lambda 表达式拆分成两部分:
() -> System.out.println("Hello Lambda!");
@Test
public void test1(){
//java8之前的写法
//jdk 1.7 前,内部类引用外部的局部变量必须是 final
String str = "java8之前的接口内部实现类写法";
Runnable r = new Runnable() {
@Override
public void run () {
System.out.println(str);
}
};
r.run();
//java8之后的写法
String str1 = "测试“无参数,无返回值”的Lambda表达式,实现接口内部实现类写法";
Runnable r1 = () -> System.out.println(str1);
r1.run();
}
结果:
java8之前的接口内部实现类写法
测试“无参数,无返回值”的Lambda表达式,实现接口内部实现类写法
(x) -> System.out.println(x)
@Test
public void test2(){
Consumer<String> consumer = (x) -> System.out.println(x);
consumer.accept("测试“有一个参数,并且无返回值”的lambda表达式");
}
结果:
测试“有一个参数,并且无返回值”的lambda表达式
x -> System.out.println(x)
对于上边的示例可以省略括号,如下:
@Test
public void test2(){
Consumer<String> consumer = x -> System.out.println(x);
consumer.accept("测试“若只有一个参数,小括号可以省略不写”的lambda表达式");
}
(x, y) -> {
//执行语句
return [返回值];
};
@Test
public void test3(){
BiFunction<Integer,Integer,Integer> biFunction = (x,y) -> {
System.out.println("测试“有两个以上的参数,有返回值,并且 Lambda 体中有多条语句”");
return x+y;
};
Integer sum = biFunction.apply(1, 2);
System.out.println(sum);
}
结果:
测试“有两个以上的参数,有返回值,并且 Lambda 体中有多条语句”
3
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
改写语法格式四的示例
@Test
public void test4 () {
BiFunction<Integer, Integer, Integer> biFunction = ( x, y ) -> x + y;
Integer sum = biFunction.apply(1, 2);
System.out.println(sum);
}
(Integer x, Integer y) -> Integer.compare(x, y);
可以省略为
(x , y) -> Integer.compare(x, y);
改写语法格式二的示例
两种写法效果相同
Consumer consumer = (String x ) -> System.out.println(x);
consumer.accept("测试“有一个参数,并且无返回值”的lambda表达式");
Consumer<String> consumer = x -> System.out.println(x);
consumer.accept("测试“有一个参数,并且无返回值”的lambda表达式");
上述Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为javac根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。
类型推断不只是在Lambda表达式
中运用,在很多地方也有使用到,比如泛型的类型推断:
Map map = new HashMap<>();
List list = new ArrayList<>();
"="号后边的泛型可以省略,用的也是类型推断(在Java7中则不支持,必须进行显示声明)。
小结:
上联:左右遇一括号省
下联:左侧推断类型省
横批:能省则省
Lambda 表达式需要“函数式接口”的支持
函数式接口:接口中只有一个抽象方法的接口,称为函数式接口。 可以使用注解 @FunctionalInterface 修饰可以检查是否是函数式接口
@FunctionalInterface 注解
,这样做可以检查它是否是一个函数式接口。同时javadoc 也会包含一条声明,说明这个接口是一个函数式接口。java.util.function包
下定义了Java 8 的丰富的函数式接口[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-htUuJFQe-1592729269559)(F:\知识库\Java\Java8新特性\Java8新特性.assets\image-20200620184440255.png)]
/**
* 自定义函数式接口
* 不带泛型
*/
@FunctionalInterface
interface MyFunctional{
/**
* 处理整数方法
* @param num 整数
* @return 处理后的整数
*/
Integer handleNumber(Integer num);
}
/**
* 自定义函数式接口
* 带泛型
*/
@FunctionalInterface
interface MyFunc<T>{
/**
* 处理泛型数据
* @param t 目标泛型数据
* @return 处理后的泛型数据
*/
T handle(T t);
}
@Test
public void test6 () {
MyFunctional myFunctional = x -> x * x;
Integer n = myFunctional.handleNumber(4);
System.out.println("测试不带泛型的自定义函数式接口,结果:" + n);
MyFunc<String> myFunc = x -> {
if (x.length() > 5) {
x=x.substring(0, 5);
}
return x;
};
String s = myFunc.handle("测试带泛型的自定义函数式接口");
System.out.println(s);
}
结果:
测试不带泛型的自定义函数式接口,结果:16
测试带泛型
实例(稍后写)
为了将Lambda 表达式作为参数传递,接收Lambda表达式的参数类型必须是与该Lambda 表达式兼容的函数式接口的类型。
类型 | 函数式接口 | 参数类型 | 返回类型 | 包含方法 | 用途 |
---|---|---|---|---|---|
消费型接口 | Consumer | T | void | void accept(T t) | 对类型为T的对象应用操作 |
供给型接口 | Supplier | 无 | T | T get() | 返回类型为T的对象 |
函数型接口 | Function |
T | R | R apply(T t) | 对类型为T的对象应用操作, 返回R类型的对象。 |
断定型接口 | Predicate | T | boolean | boolean test(T t) | 确定类型为T的对象是否满足某约束, 并返回boolean 值 |
/**
* 测试四大内置函数式接口
*/
@Test
public void test7 () {
handleStr("aaBb", x -> {
System.out.println("---测试消费型函数式接口,处理字符串---");
x = x.toUpperCase();
System.out.println(x);
});
System.out.println("---利用供给型接口:创建指定数量的随机数,存储到List并返回---");
List<Integer> list = handleStr(5, () -> (int) (Math.random() * 100));
for (Integer num : list) {
System.out.println(num);
}
System.out.println("---利用函数型接口接口,处理字符串类型数据(一个参数,有返回值);改写消费型接口的示例---");
String s = handleStr2("aaBb", x -> x = x.toUpperCase());
System.out.println(s);
System.out.println("---利用断言型接口,判断字符串是否符合条件---");
handleStr3("利用断言型接口,判断字符串是否符合条件", x -> x.length() > 3);
}
/**
* 利用消费型函数式接口,处理字符串类型数据(一个参数,无返回值)
*/
private void handleStr ( String str, Consumer<String> consumer ) {
consumer.accept(str);
}
/**
* 利用供给型接口:创建指定数量的随机数,存储到List并返回
*/
private List<Integer> handleStr ( int num, Supplier<Integer> sup ) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
Integer n = sup.get();
list.add(n);
}
return list;
}
/**
* 利用函数型接口接口,处理字符串类型数据(一个参数,有返回值)
* 改写消费型接口的示例
*/
private String handleStr2 ( String str, Function<String, String> function ) {
return function.apply(str);
}
/**
* 利用断言型接口,判断字符串是否符合条件
*/
private void handleStr3 ( String str, Predicate<String> pre ) {
if(pre.test(str)){
System.out.println("符合条件");
}
}
结果:
---测试消费型函数式接口,处理字符串---
AABB
---利用供给型接口:创建指定数量的随机数,存储到List并返回---
80
15
55
83
66
---利用函数型接口接口,处理字符串类型数据(一个参数,有返回值);改写消费型接口的示例---
AABB
---利用断言型接口,判断字符串是否符合条件---
符合条件
这些API实在四大函数式接口的基础上派生出来的,使用方法如出一辙。
序号 | 接口 & 描述 |
---|---|
1 | BiConsumer |
2 | BiFunction |
3 | BinaryOperator 代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果 |
4 | BiPredicate |
5 | BooleanSupplier 代表了boolean值结果的提供方 |
6 | Consumer 代表了接受一个输入参数并且无返回的操作 |
7 | DoubleBinaryOperator代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。 |
8 | DoubleConsumer代表一个接受double值参数的操作,并且不返回结果。 |
9 | DoubleFunction 代表接受一个double值参数的方法,并且返回结果 |
10 | DoublePredicate代表一个拥有double值参数的boolean值方法 |
11 | DoubleSupplier代表一个double值结构的提供方 |
12 | DoubleToIntFunction接受一个double类型输入,返回一个int类型结果。 |
13 | DoubleToLongFunction接受一个double类型输入,返回一个long类型结果 |
14 | DoubleUnaryOperator接受一个参数同为类型double,返回值类型也为double 。 |
15 | Function |
16 | IntBinaryOperator接受两个参数同为类型int,返回值类型也为int 。 |
17 | IntConsumer接受一个int类型的输入参数,无返回值 。 |
18 | IntFunction 接受一个int类型输入参数,返回一个结果 。 |
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 接受一个long类型输入参数,返回一个结果。 |
27 | LongPredicateR接受一个long输入参数,返回一个布尔值类型结果。 |
28 | LongSupplier无参数,返回一个结果long类型的值。 |
29 | LongToDoubleFunction接受一个long类型输入,返回一个double类型结果。 |
30 | LongToIntFunction接受一个long类型输入,返回一个int类型结果。 |
31 | LongUnaryOperator接受一个参数同为类型long,返回值类型也为long。 |
32 | ObjDoubleConsumer 接受一个object类型和一个double类型的输入参数,无返回值。 |
33 | ObjIntConsumer 接受一个object类型和一个int类型的输入参数,无返回值。 |
34 | ObjLongConsumer 接受一个object类型和一个long类型的输入参数,无返回值。 |
35 | Predicate 接受一个输入参数,返回一个布尔值结果。 |
36 | Supplier 无参数,返回一个结果。 |
37 | ToDoubleBiFunction |
38 | ToDoubleFunction 接受一个输入参数,返回一个double类型结果 |
39 | ToIntBiFunction |
40 | ToIntFunction 接受一个输入参数,返回一个int类型结果。 |
41 | ToLongBiFunction |
42 | ToLongFunction 接受一个输入参数,返回一个long类型结果。 |
43 | UnaryOperator 接受一个参数为类型T,返回值类型也为T。 |
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!)
方法引用:使用操作符“::”将方法名和对象或类的名字分隔开来。
如下三种主要使用情况:
①方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致!
②若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式: `ClassName::MethodName`
@Test
public void test8 () {
System.out.println("---方法引用 对象的引用 :: 实例方法名---");
Student student = new Student("小明", 22, Student.Gender.MAN);
Supplier<String> supplier = () -> student.getName();
//使用方法引用,可以进行如下改写
Supplier<String> supplier2 = student::getName;
String s = supplier.get();
String s2 = supplier2.get();
System.out.println(s + " " + s2 + " " + "是否相同:" + s.equals(s2));
System.out.println("---方法引用 类名 :: 静态方法名---");
Comparator<Integer> com = ( x, y ) -> Integer.compare(x, y);
//改写
Comparator<Integer> com2 = Integer::compare;
com2.compare(1, 2);
System.out.println("---方法引用 类名 :: 实例方法名 ---");
BiPredicate<String, String> bp = (x, y) -> x.equals(y);
System.out.println(bp.test("abcde", "abcde"));
//改写
BiPredicate<String, String> bp2 = String::equals;
System.out.println(bp2.test("abc", "abc"));
}
格式:ClassName::new
与函数式接口相结合,自动与函数式接口中方法兼容。
注意事项:
可以把构造器引用赋值给定义的方法,与构造器参数列表要与接口中抽象方法的参数列表一致!
@Test
public void test9 () {
System.out.println("---测试构造器引用---");
Function<String, String> fun = String::new;
String s = fun.apply("测试");
System.out.println(s);
//使用无参构造器引用,创建对象
Supplier<Student> student = Student::new;
}
格式:type[] :: new
@Test
public void test10() {
System.out.println("---测试数组引用---");
Function<Integer, String[]> fun = String[]::new;
String[] strs = fun.apply(10);
System.out.println(strs.length);
}