3.lambda表达式
3.1 概念
带参数变量的表达式被称为lambda表达式。lambda表达式是一个可传递的代码块,在Java8被引入,使用场景为替换需要使用接口类型的变量。
lambda表达式的引入使Java提供了对函数式编程的支持。关于函数式编程是什么,见:https://www.cnblogs.com/zjq-blog/p/15518023.html
考虑Arrays.sort方法,调用形式主要分为两种:
Arrays.sort(数组,[由下标指示的排序范围]);
Arrays.sort(数组,[由下标指示的排序范围,] 比较器);
不考虑数组排序范围这个可选参数,对于第一种方式,需要数组的元素实现Comparable接口。这样就需要重写CompareTo方法,排序方法会用CompareTo的方法返回规则。
public int compareTo(Integer anotherInteger) { //Integer类中重写compareTo方法
return compare(this.value, anotherInteger.value);
}
那如果相对一个数组排序,但数组元素没有实现Comparable接口呢?这时就需要再传入一个比较器Comparator
Employee[] array = new Employee[10];
...添加元素到array中
//实现接口方式
public class MyComparator implements Comparator<Employee> {
@Override
public int compare(Employee o1, Employee o2) {
return (int)(o1.getSalary() - o2.getSalary());
}
}
Arrays.sort(array, new MyComparator()); //传入对象
//匿名内部类的方式
Arrays.sort(array, new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
return (int)(o1.getSalary() - o2.getSalary());
}
});
也就是说,我们传入的是一个指定元素比较的规则。那么既然我们只需要这个规则就可以支持比较,可不可以用更简单的方式呢?这就是采用lambda表达式的方式。
Arrays.sort(array, (o1, o2) -> (int)(o1.getSalary() - o2.getSalary()));
其实lambda表达式就相当于一个方法的简写,指定了参数和方法中的逻辑。
3.2 语法
·最基础的语法:(参数列表) -> {代码段} //无需写返回类型,因为编译器可以根据上下面推导出来
·若方法没有参数仍需要括号:() -> {do something..}
·参数类型可以被推导出来,则类型可以省略:Comparator
·代码只有一行可以不加代码段{ }: (o1, o2) -> (int)(o1.getSalary() - o2.getSalary())
·只有一个参数可以不加参数括号: list.removeIf(e -> e == null);
4.函数式接口
在上文可以看到,有些接口的目的就是其类型变量可以作为参数而生的,因为他会规范实现类需要具有某个方法,比如Comparator。
这种只有一个抽象方法的接口称为函数式接口。
可以使用@FunctionalInterface注解来标记这样的接口,防止编写不规范。
5.方法引用与构造器引用
5.1 概念
我们知道,lambda的实际上就是传入了一个简写的方法,如 x -> System.out.println(x)。
那么,加入这个方法的代码块逻辑不需要我们手动编写,而是已经有现成的方法实现,可不可以直接传入一个已经写好的方法呢?这就是方法引用,只需传入:
System.out::println //等价于 x -> System.out.println(x)
也就是说,其本质传入一个已经存在的函数,更接近函数式编程的思想。
5.2 形式
使用"::"分隔类名与方法名。类名的写法与调用方法时相同,要么写全路径,要么通过import提前引入。
·aObject::aMethod([可选的参数列表]) //实例方法,aObject为实例对象,aMethod为对象的方法
·aClass::aStaticMethod([可选的参数列表]) //静态方法,aClass为类,aStaticMethod为类的静态方法
·aClass::aMethod(aObject,[可选的参数列表]) //实例方法第二种形式,aClass为类,aObject为实例对象要写在括号中。
5.3 构造器引用
构造器引用与方法引用类似,只不过形式为:
类::new
如:Employee::new、int[]::new
具体调用那个构造器取决于上下文。
方法引用和Lambda表达式可以让实现某些复杂功能的代码更简洁明了。例如:对于Arrays.sort方法,可以将Comparator写为更高级的形式以获取更多的功能。
如果我想首先根据员工的姓名按自然顺序(a在b前)排序,同时允许姓名出现null,null值排到最后。如果名字相同,再按工资排序,工资较少的排在前面,同样允许出现null,该怎么做?
在比较器位置传入一个Comparator.comparing(keyExtractor, keyComparator)方法,第一个参数为键比较器,返回一个待比较对象的域;第二个参数需要传入一个能够对第一个值进行比较的方法。同时comparing方法还会返回一个比较器可以调用thenComparing方法形成比较链,当上一个域相同时按顺序比较下一个域。如果允许出现null值,可以对keyComparator使用Comparator.nullsLast()或者nullsfirst()包装一下,分别表示null值排到最后或最前面。
Employee[] e = new Employee[4];
e[0] = new Employee(100, "a1");
e[1] = new Employee(100, "b1");
e[2] = new Employee(1000, "a1");
e[3] = new Employee(1000, null);
Arrays.sort(e, Comparator.comparing(Employee::getName,
Comparator.nullsLast(Comparator.naturalOrder())).thenComparing(Employee::getSalary, Comparator.nullsLast((a, b) -> (int)(a - b))));
System.out.println(Arrays.toString(e));
5.4 闭包
lambda表达式可以捕获外围作用域中引用值不会改变的变量的值。(该变量必须初始化后就不能再改变值)