Lambda表达式
什么是Lambda表达式
Lambda 是一个匿名函数,我们可以把 Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
举个栗子
//从匿名内部类到Lambda的转化
public void test1() {
//匿名内部类实现线程接口
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello World!");
}
};
//Lambda表达式实现线程接口
Runnable r2 = () -> System.out.println("Hello World!");
}
善变的需求
在工作中我们可能经常遇到对一堆固定数据这样儿那样儿的需求。比如:
public List<Student> stus = Arrays.asList(
new Student("小康", 16, 98.5),
new Student("小陈", 28, 60),
new Student("小伟", 20, 42),
new Student("小孙", 8, 85)
);
- 筛选出60分以上的学生;
- 筛选出18岁以下的学生;
- 筛选出姓陈的学生;
- 对学生进行分数正向排序
- …
策略模式
将筛选条件抽象成方法复用 - - - ? 产生大量冗余代码,难以维护。
传统设计模式中策略模式可以解决此类问题。
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
通过行为族的规定去替换规则策略。
匿名内部类减少策略类
直接通过匿名内部类的形式实现规则接口,减少大量的策略类。
//匿名内部类
@Test
public void test3() {
//获取18岁以下的学生
List<Student> ageStus = filterStudent(stus, new FilterRule<Student>() {
@Override
public boolean myRule(Student student) {
return student.getAge() < 18;
}
});
//获取60分以上的学生
List<Student> scoreStus = filterStudent(stus, new FilterRule<Student>() {
@Override
public boolean myRule(Student student) {
return student.getAge() >= 60;
}
});
}
通过Lambda表达式减少匿名内部类代码
通过Lambda表达式可以有效减少大量的匿名内部类代码。
//Lambda表达式
@Test
public void test4() {
//获取18岁以下的学生
List<Student> ageStus = filterStudent(stus, (s) -> s.getAge() <= 18);
//获取60分以上的学生
List<Student> scoreStus = filterStudent(stus, (s) -> s.getScore() >= 60);
}
Lambda语法
我们可以看出Lambda主要的目的还是改造匿名内部类对接口的实现上。我们可以把整个Lambda体的结果当做为一个接口的匿名内部类实现后返回的结果。
箭头操作符:
Lambda 表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “ ->” , 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:
左侧: 指定了 Lambda 表达式需要的所有参数。
右侧: 指定了 Lambda 体,即 Lambda 表达式要执行的功能。
基本语法:
- 语法格式一: 无参,无返回值, Lambda 体只需一条语句
Runnable runnable = () -> System.out.println("Hello World");
- 语法格式二: 需要一个参数(此时小括号可以省略
Consumer<String> consumer1 = (str) -> System.out.println(str);
Consumer<String> consumer2 = str -> System.out.println(str);
- 语法格式三: 需要两个参数,并且有返回值(多条语句必须有大括号)
BinaryOperator<Integer> bo = (Integer x, Integer y) -> {
System.out.println(x + "+" + y + "=");
return x + y;
};
- 语法格式四:当 Lambda 体只有一条语句时, return 与大括号可以省略
BinaryOperator<Integer> bo2 = (Integer x, Integer y) -> x + y;
Runnable 、Consumer、BinaryOperator、BinaryOperator这些接收返回值的都是接口类型,后边Lambda体相当于一个匿名内部类对接口的实现。
思考:语法中所提的参数、返回值是哪的?接口中哪个方法的?
函数式接口
什么是函数式接口:
只包含一个抽象方法的接口,称为函数式接口。
使用:
- 可以通过 Lambda 表达式来创建该接口的对象。
- 接口上使用 @FunctionalInterface注解,可以检查它是否是函数式接口。
- 若不是函数式接口,创建Lambda表达式时报错。
声明一个函数式接口:
作为参数传递Lambda表达式:
- 为了将 Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。
Java内置函数式接口:
接口 | 函数描述符 | 方法名 |
---|---|---|
Predicate断言型接口 | T->boolean | 确定类型为T的对象是否满足某约束,并返回boolean 值。包含方法boolean test(T t) |
Consumer消费型接口 | T->void | 对类型为T的对象应用操作,包含方法:void accept(T t) |
Function<T,R>函数型接口 | T->R | 对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法: R apply(T t); |
Supplier供给型接口 | ()->T | 返回类型为T的对象,包含方法:T get(); |
函数式接口使用实例:
- 消费型接口: 参数有来无回
public void shopping(Double money, Consumer<Double> con) {
con.accept(money);
}
//Consumer<T> 消费型接口 :
@Test
public void test1() {
shopping(666.8, (m) -> System.out.println("逛淘宝消费了:" + m + "元"));
}
- 供给型接口: 无传参 但返回资源
//产生指定个数的随机数返回
public List<Integer> getNumList(int count, Supplier<Integer> sup) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < count; i++) {
Integer n = sup.get();
list.add(n);
}
return list;
}
//Supplier<T> 供给型接口 :
@Test
public void test2() {
List<Integer> numList = getNumList(10, () -> (int) (Math.random() * 100));
}
- 函数型接口: 参数和返回值类型可不同可相同
//获取字符串长度
public Integer getLength(String str, Function<String,Integer> fun) {
return fun.apply(str);
}
//Function<T, R> 函数型接口:
@Test
public void test3() {
Integer chenLen = getLength("ChenYouXiu", (str) -> str.length());
}
- 断言型接口: 返回值为布尔
//需求:将满足条件的字符串,放入集合中
public List<String> filterStr(List<String> list, Predicate<String> pre){
List<String> strList = new ArrayList<>();
for (String str : list) {
if(pre.test(str)){
strList.add(str);
}
}
return strList;
}
//Predicate<T> 断言型接口:
@Test
public void test4(){
List<String> strList = filterStr(Arrays.asList("Hello", "JK", "Lambda", "www", "ok"),
(s) -> s.length() > 3);
}
- 还有许多JDK提供的函数式子接口 可以多多探索
方法引用、构造器引用
这两种引用我理解为Lambda语法中的一种特殊语法。
方法引用
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用。
原则: 函数式接口中方法的参数列表,必须与方法引用方法的参数列表保持一致!
语法:
使用操作符两个冒号 “::” 将方法名和对象或类的名字分隔开来。三种主要使用情况:
- 对象::实例方法
- 类::静态方法
- 类::实例方法
实例:
Consumer<String> con1 = (x)-> System.out.println(x);
Consumer<String> con2 = System.out::println;
con1.accept("Hello Java8!");
con2.accept("Hello Java8!");
只有参数数量一致,才可以使用方法引用。
构造器引用
格式: ClassName::new
说明: 与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义的方法,与构造器参数列表要与接口中抽象方法的参数列表一致!