Lambda表达式可以理解为一种匿名函数:它没有名称,但是由参数列表,函数主体,返回类型,当然可能还有一个可以抛出的异常的列表。
Lambda的基本语法是
(parameters) -> expression
或(请注意语句的花括号)
(parameters) -> { statements; }
根据上述语法规则,以下哪个不是有效的Lambda表达式?
(1) () -> {}
(2) () -> "Raoul"
(3) () -> {return "Mario";}
(4) (Integer i) -> return "Alan" + i;
(5) (String s) -> {"IronMan";}
答案:只有4和5是无效的Lambda。
(1) 这个Lambda没有参数,并返回void。它类似于主体为空的方法:public void run() {}。
(2) 这个Lambda没有参数,并返回String作为表达式。
(3) 这个Lambda没有参数,并返回String(利用显式返回语句)。
(4) return是一个控制流语句。要使此Lambda有效,需要使花括号,如下所示:
(Integer i) -> {return "Alan" + i;}。
(5)“Iron Man”是一个表达式,不是一个语句。要使此Lambda有效,你可以去除花括号
和分号,如下所示:(String s) -> "Iron Man"。或者如果你喜欢,可以使用显式返回语
句,如下所示:(String s)->{return "IronMan";}。
这里我们从一个排序问题入手——用不同的排序策略来给一个Apple列表排序,我们从一个原始粗暴的解决方法一步步简明清晰化。
这里会用到一系列概念:行为参数化,匿名类,Lambda表达式和方法引用。
下面是Apple类的定义:
public static class Apple {
private Integer weight = 0;
private String color = "";
public Apple(Integer weight, String color) {
this.weight = weight;
this.color = color;
}
public Integer getWeight() {
return weight;
}
public void setWeight(Integer weight) {
this.weight = weight;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String toString() {
return "Apple{" + "color='" + color + '\'' + ", weight=" + weight + '}';
}
}
第一步:传递代码
Java 8的API已经为我们提供了一个List可用的sort方法,我们不用自己去实现它。
那么最困难的部分已经搞定了!但是,如何把排序策略传递给sort方法呢?
sort方法的签名是这样的:
void sort(Comparator<? super E> c)
它需要一个Comparator对象来比较两个Apple!这就是在Java中传递策略的方式:它们必须包裹在一个对象里。我们说sort的行为被参数化了:传递给它的排序策略不同,其行为也会不同。
List<Apple> inventory = new ArrayList<>();
public static class AppleComparator implements Comparator<Apple> {
public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight());
}
}
inventory.sort(new AppleComparator());
第二步:使用匿名内部类
第一步的方案可以使用匿名内部类来进行优化,因为一个AppleComparator 比较器可能就使用一次,我们不需要单独创建一个类来实现它。
inventory.sort(new Comparator<Apple>() {
public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight());
}
});
第三步:使用Lambda表达式
使用匿名内部类的缺点:
1.它往往很笨重,占用了很多代码空间,换句话来说,它太啰嗦了。
2.用起来让人费解,逻辑不是很清晰。
Java 8中引入了Lambda表达式,它提供了一种轻量级的语法来实现相同的目标:传递代码。(放在我们这个问题里就是传递策略,List#sort()方法根据不同的策略(代码)来实现不同的排序)。
在这个例子中,Comparator代表了函数描述符(T, T) -> int。因为我们比较的是苹果,所以它具体代表的就是(Apple, Apple) -> int。
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
因为,Java编译器可以根据Lambda出现的上下文来推断出Lamdba表达式参数的类型,那么我们的解决方案可以改写为:
inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));
到这里我们依旧不满足,Comparator具有一个叫作comparing的静态辅助方法,它可以接受一个Function来提取Comparable键值,并生成一个Comparator对象。
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
它可以像下面这样用(注意你现在传递的Lambda只有一个参数:Lambda说明了如何从苹果中提取需要比较的键值):
Comparator<Apple> c = Comparator.comparing((Apple a) -> a.getWeight()); inventory.sort(c);
这里我们还可以把代码改的紧凑一些:
// 静态导包
import static java.util.Comparator.comparing;
inventory.sort(comparing((a) -> a.getWeight()));
第四步:使用方法引用
方法引用就是替代那些转发参数的Lambda表达式的语法糖。你可以用方法引用让你的代码更简洁(假设你静态导入了java.util.Comparator.comparing):
inventory.sort(comparing(Apple::getWeight));
方法引用让你重复使用现有的方法实现并直接传递它们。
恭喜你,这就是你的最终解决方案!这比Java 8之前的代码好在哪儿呢?
它比较短;它的意思也很明显,并且代码读起来和问题描述差不多:“对库存进行排序,比较苹果的重量。”