Lambda表达式
一、Lambda表达式简介
什么是Lambda?
Lambda是 JAVA 8添加的新特性,说白了,Lambda是一个匿名函数
同时,它也称为闭包,Lambda表达式允许把函数作为一个方法的参数(函数作为参数传递到方法中)
为什么使用Lambda
使用Lambda表达式可以对一个接口的方法进行非常简洁的实现
Lambda对接口的要求
虽然可以使用Lambda表达式对某些接口进行简单的实现,但是并不是所有的接口都可以用Lambda表达式来实现,要求接口中定义的必须要实现的抽象方法只能是一个
在JAVA8中 ,对接口加了一个新特性:default
可以使用default对接口方法进行修饰,被修饰的方法在接口中可以默认实现
@FunctionalInterface
修饰函数式接口的,接口中的抽象方法只有一个
二、Lambda的基础语法
1.语法
// 1.Lambda表达式的基础语法
// Lambda是一个匿名函数 一般关注的是以下两个重点
// 参数列表 方法体
/**
* ():用来描述参数列表
* {}:用来描述方法体 有时可以省略
* ->: Lambda运算符 读作goes to
* 例 Test t=()->{System.out.println("hello word")}; 大括号可省略
*/
2.创建多个函数式接口
/**
* Created by FengBin on 2021/8/12 19:03
* 无返回值无参数的函数式接口
*/
@FunctionalInterface
public interface NoneReturnNoneParameter {
void test();
}
/**
* Created by FengBin on 2021/8/12 19:03
* 无返回值单个参数的函数式接口
*/
@FunctionalInterface
public interface NoneReturnSingleParameter {
void test(int num);
}
/**
* Created by FengBin on 2021/8/12 19:04
* 无返回值多个参数的函数式接口
*/
@FunctionalInterface
public interface NoneReturnMultipleParameter {
void test(int num1, int num2);
}
/**
* Created by FengBin on 2021/8/12 19:11
* 单个返回值无参数的函数式接口
*/
@FunctionalInterface
public interface SingleReturnNoneParameter {
int test();
}
/**
* Created by FengBin on 2021/8/12 19:11
* 单个返回值单个参数的函数式接口
*/
@FunctionalInterface
public interface SingleReturnSingleParameter {
int test(int num);
}
/**
* Created by FengBin on 2021/8/12 19:11
* 单个返回值多个参数的函数式接口
*/
@FunctionalInterface
public interface SingleReturnMutipleParameter {
int test(int num1, int num2);
}
3.创建测试类
package com.feng.lambda.lambda_test.test;
import com.feng.lambda.lambda_test.lambda_interface.*;
import org.junit.jupiter.api.Test;
/**
* Created by FengBin on 2021/8/12 19:06
*/
public class TestLambdaInterface {
@Test
public void test01() {
//测试无返回值无参数的函数式接口
NoneReturnNoneParameter lambda = () -> {
System.out.println("无返回值无参数接口的Lambda表达式...");
};
lambda.test();
}
@Test
public void test02() {
//测试无返回值单个参数的函数式接口
NoneReturnSingleParameter lambda = (int num) -> {
System.out.println(num);
};
lambda.test(10);
}
@Test
public void test03() {
//测试无返回值多个参数的函数式接口
NoneReturnMultipleParameter lambda = (int num1, int num2) -> {
System.out.println(num1 + num2);
};
lambda.test(10,20);
}
@Test
public void test04() {
//测试有返回值无参数的函数式接口
SingleReturnNoneParameter lambda = () -> {
System.out.println("有返回值无参数的Lambda表达式...");
return 10;
};
lambda.test();
}
@Test
public void test05() {
//测试有返回值单个参数的函数式接口
SingleReturnSingleParameter lambda = (int num) -> {
return num;
};
int res = lambda.test(10);
System.out.println(res);
}
@Test
public void test06() {
//测试有返回值多个参数的函数式接口
SingleReturnMutipleParameter lambda = (int num1,int num2) -> {
return num1 + num2;
};
int res = lambda.test(10,20);
System.out.println(res);
}
}
三、语法精简
针对上述基础语法的精简
1.参数类型精简
//1. 对参数类型进行精简:由于在接口中我们已经定义了参数的类型,故我们在使用Lambda表达式的时候,可以省略参数类型,自动为我们匹配类型
NoneReturnMultipleParameter lambda = (int num1, int num2) -> {
System.out.println(num1 + num2);
};
//对于上面的Lambda表达式我们进行如下的精简
NoneReturnMultipleParameter lambdaSimple = (num1,num2) -> {
System.out.println(num1 + num2);
};
2.参数小括号精简
//2. 如果接口的方法的参数列表中只有一个参数,则我们可以省略小括号
NoneReturnSingleParameter lambda2 = (num) -> {
System.out.println(num);
};
//对于上面的Lambda表达式我们进行如下的精简
NoneReturnSingleParameter lambda2Simple = num -> {
System.out.println(num);
};
3.方法大括号精简
//3. 如果方法体只有一条语句,我们省略大括号
NoneReturnSingleParameter lambda3 = num -> System.out.println(num);
4.大括号精简补充
//4. 如果方法体的唯一一条语句是返回语句,则我们在省略大括号的同时,可以省略return
SingleReturnSingleParameter lambda4 = num -> num;
lambda4.test(10);
5.多参数,有返回值 精简
//5. 如果存在多个参数,同样可以精简
SingleReturnMutipleParameter lambda5 = (num1,num2) -> num1 + num2;
int result = lambda5.test(10, 20);
System.out.println(result);
四、Lambda语法进阶
1.方法引用(普通方法与静态方法)
我们先定义一个函数式接口:MethodReference
@FunctionalInterface
public interface MethodReference {
int test(int num);
}
在实际应用过程中,一个接口在很多地方都会调用同一个实现,例如:
@Test
public void test01() {
MethodReference method1 = num -> num * 2;
MethodReference method2 = num -> num * 2;
MethodReference method3 = num -> num * 2;
int res1 = method1.test(10);
int res2 = method2.test(20);
int res3 = method3.test(30);
System.out.println(res1);
System.out.println(res2);
System.out.println(res3);
}
这样一来每次都要写上具体的实现方法 a+b,如果需求变更,则每一处实现都需要更改,基于这种情况,可以将后续的是实现更改为已定义的 方法,需要时直接调用就行
语法:
/**
*方法引用:
* 可以快速的将一个Lambda表达式的实现指向一个已经实现的方法
* 方法的隶属者 如果是静态方法 隶属的就是一个类 其他的话就是隶属对象
* 语法:方法的隶属者::方法名
* 注意:
* 1.引用的方法中,参数数量和类型一定要和接口中定义的方法一致
* 2.返回值的类型也一定要和接口中的方法一致
*/
例:
@Test
public void test02() {
MethodReference method1 = num -> change(num);
MethodReference method2 = num -> change(num);
MethodReference method3 = num -> change(num);
System.out.println(method1.test(10));
System.out.println(method2.test(20));
System.out.println(method3.test(30));
}
/*
* 自定义实现的方法:需要保证参数的数量和类型一定要和接口中定义的方法一致
* */
public static int change(int num) {
return num * 2;
}
package com.alan.learn.syntax;
import com.alan.learn.interfaces.LambdaSingleReturnSingleParmeter;
public class Syntax3 {
public static void main(String[] args) {
LambdaSingleReturnSingleParmeter lambda1=a->a*2;
LambdaSingleReturnSingleParmeter lambda2=a->a*2;
LambdaSingleReturnSingleParmeter lambda3=a->a*2;
//简化
LambdaSingleReturnSingleParmeter lambda4=a->change(a);
//方法引用
LambdaSingleReturnSingleParmeter lambda5=Syntax3::change;
}
/**
* 自定义的实现方法
*/
private static int change(int a){
return a*2;
}
}
此时我们可以利用方法引用的方式继续优化Lambda表达式:
@Test
public void test03() {
//change是类的静态方法,我们选择使用 类名::方法名
MethodReference method1 = TestMethodReference::change;
MethodReference method2 = TestMethodReference::change;
MethodReference method3 = TestMethodReference::change;
System.out.println(method1.test(10));
System.out.println(method2.test(20));
System.out.println(method3.test(30));
}
2.方法引用(构造方法)
目前有一个实体类
public class Person {
String name;
int age;
public Person() {
System.out.println("Person 无参构造...");
}
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Person 有参构造...");
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
需求
两个接口,各有一个方法,一个接口的方法需要引用Person的无参构造,一个接口的方法需要引用Person的有参构造 用于返回两个Person对象,例:
public interface PersonCreater1 {
Person getPerson();
}
public interface PersonCreater2 {
Person getPerson(String name,int age);
}
那么可以写作:
@Test
public void test04() {
PersonCreater1 creater1 = Person::new;
Person person = creater1.getPerson();
System.out.println(person);
}
@Test
public void test05() {
PersonCreater2 creater2 = Person::new;
Person person = creater2.getPerson("zhangsan",24);
System.out.println(person);
}
注意:是引用无参构造还是引用有参构造 在于接口定义的方法参数
五、综合练习
1.集合排序案例
//将List<Person>集合按照Person的年龄进行排序
@Test
public void test01() {
List<Person> list = new ArrayList<>();
list.add(new Person("zhangsan",12));
list.add(new Person("lisi",32));
list.add(new Person("wangwu",20));
list.add(new Person("zhaoliu",5));
list.add(new Person("fengqi",26));
//使用lambda表达式进行排序
list.sort(((o1, o2) -> o2.age - o1.age));
System.out.println("按照年龄降序以后的list集合:" + list);
}
2.Treeset排序案例
//使用TreeSet进行排序:在TreeSet中如果Comparator返回值是 0 会判断这是两个元素是相同的,会进行去重,我们需要手动实现
@Test
public void test02() {
TreeSet<Person> set = new TreeSet<>((o1, o2) -> {
if(o1.age >= o2.age) {
return -1;
}else {
return 1;
}
});
set.add(new Person("zhangsan",12));
set.add(new Person("lisi",32));
set.add(new Person("wangwu",20));
set.add(new Person("zhaoliu",5));
set.add(new Person("fengqi",26));
System.out.println(set);
}
3.集合的遍历
/*
* list.forEach(Consumer<? super E> action)
* api文档解释:对集合中的每个元素执行给定的操作,直到所有元素都被处理或动作引发异常。
* 将集合中的每一个元素都带入到接口Consumer的方法accept中,然后方法accept指向我们的引用
* 输出集合中的所有元素
* list.forEach(System.out::println);
* */
@Test
public void test03() {
List<Person> list = new ArrayList<>();
list.add(new Person("zhangsan",12));
list.add(new Person("lisi",32));
list.add(new Person("wangwu",20));
list.add(new Person("zhaoliu",5));
list.add(new Person("fengqi",26));
//输出全部元素
list.forEach(System.out::println);
//输出Person中age为偶数的值
list.forEach(item -> {
if (item.age % 2 == 0) {
System.out.println(item);
}
});
}
4.删除集合中满足条件的元素
/*
* 删除集合中的满足条件的元素 : 将集合中每一个元素带入到接口Predicate的test方法中,如果返回值是true,则删除这个元素
* */
@Test
public void test04() {
List<Person> list = new ArrayList<>();
list.add(new Person("zhangsan",12));
list.add(new Person("lisi",32));
list.add(new Person("wangwu",20));
list.add(new Person("zhaoliu",5));
list.add(new Person("fengqi",26));
/*这里我们先按照原来的实现方式:采用迭代器实现
Iterator<Person> it = list.iterator();
while (it.hasNext()) {
Person person = it.next();
if (person.age > 20) {
it.remove(); //这里注意不能使用list本身移除,会报并发修改异常
}
}*/
list.removeIf(item -> item.age > 20);
System.out.println(list);
}
5.开辟一条线程 做一个数字的输出
/**
* 需求:
* 开辟一条线程 做一个数字的输出
*/
public class Test {
public static void main(String[] args) {
/**
* 通过Runnable 来实例化线程
*/
Thread t=new Thread(() -> {
for(int i = 0;i < 100;i++){
System.out.println(i);
}
});
t.start();
}
}
六、系统内置的函数式接口
import java.util.function.*;
/**
* 系统内置的一些函数式接口
*/
public class FunctionalInterface {
public static void main(String[] args) {
// Predicate<T> : 参数是T 返回值boolean
// 在后续如果一个接口需要指定类型的参数,返回boolean时可以指向 Predicate
// IntPredicate int -> boolean
// LongPredicate long -> boolean
// DoublePredicate double -> boolean
// Consumer<T> : 参数是T 无返回值(void)
// IntConsumer int ->void
// LongConsumer long ->void
// DoubleConsumer double ->void
// Function<T,R> : 参数类型T 返回值R
// IntFunction<R> int -> R
// LongFunction<R> long -> R
// DoubleFunction<R> double -> R
// IntToLongFunction int -> long
// IntToDoubleFunction int -> double
// LongToIntFunction long -> int
// LongToDoubleFunction long -> double
// DoubleToLongFunction double -> long
// DoubleToIntFunction double -> int
// Supplier<T> : 参数 无 返回值T
// UnaryOperator<T> :参数T 返回值 T
// BiFunction<T,U,R> : 参数 T、U 返回值 R
// BinaryOperator<T> :参数 T、T 返回值 T
// BiPredicate<T,U> : 参数T、U 返回值 boolean
// BiConsumer<T,U> : 参数T、U 无返回值
}
}
常见的函数式接口:
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer消费型接口 | T | void | 对类型为T的对象应用操作,包含方法:void accept(T t); |
Supplier 供给型接口 | 无 | T | 返回类型为T 的对象,包含方法:T get(); |
Function 函数型接口 | T | R | 对类型为T 的对象应用操作,并返回结果。结果R 类型。包含方法:R apply(T t); |
Predicate 断定型接口 | T | boolean | 确定类型为T 的对象是否满足某约束,并返回boolean值。包含方法boolean test(T t); |
七、Lambda闭包
/**
* Created by FengBin on 2021/8/13 10:26
* 闭包问题:lambda的闭包可以提升包围变量的生命周期
* 故我们的局部变量在lambda表达式被引用,此时不会因为方法的结束而销毁,使得我们能在该方法的外部获取到该局部变量的值
*/
public class TestClosure {
public static void main(String[] args) {
int result = getNumber().get();
System.out.println(result);
}
public static Supplier<Integer> getNumber() {
int num = 10;
//Supplier:无参数,单个单个返回值
return () -> {
return num;
};
}
}
/**
* Created by FengBin on 2021/8/13 10:30
* 对于闭包中变量必须是一个常量,在编译的过程中,自动为其添加final修饰符,故我们对于闭包中的变量不用进行任何的修改,否则会报错
*/
public class TestCosure02 {
public static void main(String[] args) {
int num = 10;
//Consumer 单个参数,没有返回值
Consumer<Integer> c = item -> {
System.out.println(num);
};
//num++; 不要尝试修改闭包中的变量的值,这样会使得编译报错的!!!
//这里输入的参数并不影响最后的结果,都是返回10
c.accept(1);
}
}
输出结果:
10