Lambda粗解
案例总结:点此传送门
inventory.sort(comparing(Apple::getWeight));
Lambda表达式必须基于一个接口类,该类中只有一个抽象方法。使用分为三步:
- 定义一个接口类,包含一个抽象方法。
- 创建该接口类的实例对象,并实现抽象方法(使用Lambda表达式实现)。
- 直接调用抽象方法,或将实例对象传给某个方法。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
public static <T> void forEach(List<T> list, Consumer<T> c) {
for (T i : list) {
c.accept(i);
}
}
public static void main(String[] args) {
// forEach(Arrays.asList(1,2,3,4,5), (Integer i) -> System.out.println(i));
Consumer<Integer> c = (Integer i) -> System.out.println(i);
c.accept(6); // 直接调用抽象方法
forEach(Arrays.asList(1,2,3,4,5), c); // 行为传递
//直接将lambda表达式作为参数传递给forEach方法,实际传递的是Consumer接口类的实例对象。
//lambda表达式即accept方法的具体实现,标签为Integer -> void,即T由Integer特化
//forEach(Arrays.asList(1,2,3,4,5), (Integer i) -> System.out.println(i))
}
结果如图:需要注意的是创建接口对象的同时实现抽象方法,传递行为实际上传递的是接口对象(被传递的方法如forEach方法在内部有调用接口的抽象方法accept,而accept方法的实现是在创建对象时实现的,就是lambda表达式),也可直接将lambda表达式作为参数(代表一种行为)传递给forEach方法(实际上传递的是接口实例对象)。

类型检查、类型推断以及限制
Lambda表达式本身不不包含它在实现哪个函数式接口的信息。需要通过类型检查、类型推断来判断。使用Lambda表达式时一定要对函数式接口及其抽象方法有所了解。
类型检查
Lambda的类型是从使用Lambda的上下文推断出来的。有以下两种情况,这里的参数和局部变量实际上都是函数式接口创建的对象。
- 接受它传递的方法的参数
- 接受它的值的局部变量。根据局部变量的不同,Lambda表达式可用于多个不同的函数式接口(作为参数传入方法时同理,只是由于被传入的方法已经确定了参数)
/**
* 此处为第一种情况
* Lambda表达式为 (Apple a) -> a.getWeight() > 150
* 签名为 Apple -> boolean
* 这里是通过filter方法的参数列表来确定Lambda表达式具体实现的是哪个函数式接口
* filter(List<T>, Predicate<T>)
* 可以确定Lambda实现的是Predicate函数式接口
* 对应实现的抽象方法是Predicate函数式接口中的test方法
* 签名为 T -> booleab,T绑定Apple,能正确匹配
*/
List<Apple> heavierThan150g = filter(inventory, (Apple a) -> a.getWeight() > 150);
/**
* 此处为第二种情况
* Lambda表达式均为 (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())
* 签名为 (Apple, Apple) -> int
* 这里是通过局部变量来确定Lambda表达式具体实现的函数式接口
* 分别为Comparator(compare), ToIntBiFunction(applyAsInt), BiFunction(apply)
* 确定了函数式接口也就确定了实现的抽象方法
* compare方法的标签(T, T) -> int
* applyAsInt方法的标签(T, U) -> int
* apply方法的标签(T, U) -> int
* T和U均绑定到Apple,即符合Lambda表达式的标签,所以此处的Lambda可以同时适配不同的函数式接口
*/
Comparator<Apple> c1 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
ToIntBiFunction<Apple, Apple> c2 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
BiFunction<Apple, Apple, Integer> c3 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
类型推断
为了进一步简化代码,Java编译器会从上下文中推断出用什么函数式接口来配合Lambda,也意味着它能推断出适合Lambda的签名,因为函数描述符可以通过目标类型来得到。
如下面代码,可以在Lambda语法中省去标注参数类型。
// 通过目标类型T绑定Apple可以不显式的指明参数类型。
List<Apple> heavierThan150g = filter(inventory, a -> a.getWeight() > 150);
List<Apple> heavierThan150g = filter(inventory, (Apple a) -> a.getWeight() > 150);
// 通过目标类型T绑定Apple可以不显式的指明参数类型。
Comparator<Apple> c1 = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
Comparator<Apple> c1 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
使用局部变量的限制
在Lambda表达式中使用外层作用域中定义的局部变量时,必须将该局部变量显式声明为final,不推荐在Lambda表达式中使用外部局部变量,会使情况变得复杂。
方法引用
方法引用就是根据已有的方法实现来创建Lambda表达式。显式的指定方法名称,会使代码可读性更好。
// 方法引用,没有实际调用方法
Apple::getWeight;
// Lambda表达式,标签为 Apple -> int
(Apple a) -> a.getWeight();
如何构建方法引用
方法引用主要有三类:
- 指向静态方法的方法引用(例如Integer的parseInt方法,写作Integer::parseInt)。
- 指向任意类型实例方法的方法引用(例如String的length方法,写作String:length)。
- 指向现有对象的实例方法的方法引用(假设有一个局部变量expensiveTranscation用于存放Transcation类型的对象,它支持实例方法getValue,那么就可以写expensiveTranscation::getValue)。
/**
* 第二种方法的思想:
* 引用一个对象,而这个对象本身是Lambda的一个参数。
* 此处引用的对象方法是String类型对象s的toUpperCase方法,而s是Lambda的一个参数。
*/
String::toUpperCase;
(String s) -> s.toUpperCase();
/**
* 第三种方法的思想:
* 在Lambda中调用一个已经存在的外部对象中的方法。
* 此处的expensiveTranscation是在外部定义的实例对象,即现有对象,并调用该对象的getValue方法。
*/
expensiveTranscation::getValue
() -> expensiveTranscation.getValue();
方法引用实例
//Comparator接口类
@FunctionalInterface
public interface Comparator<T> {
...
int compare(T o1, T o2);
...
}
//List接口类
public interface List<E> extends Collection<E> {
...
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
...
}
//String类
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
...
public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();
...
public int compareToIgnoreCase(String str) {
return CASE_INSENSITIVE_ORDER.compare(this, str);
}
...
}
public static void main(String[] args) {
/**
* List类的sort方法接收一个Comparator作为对象(Lambda表达式作为参数传入方法,实际是对象)
* Comparator中抽象方法compare方法签名为(T, T) -> int
* String类中定义的compareToIgnoreCase方法返回值为int类型
* 从源码中看compareToIgnoreCase方法实际上使用的还是Comparator类型对象的compare方法
*/
List<String> str = Arrays.asList("a", "b", "A", "B");
tr.sort((String s1, String s2) -> s1.compareToIgnoreCase(s2));
System.out.println(str);
//Lambda调用的是参数对象s1的方法,符合第二种方法,可用方法引用方式简化代码
List<String> str2 = Arrays.asList("c", "d", "C", "D");
str2.sort(String::compareToIgnoreCase);
System.out.println(str2);
}
结果如图:Lambda表达式与方法引用等效

构造函数引用
构造函数引用与指向静态方法的引用类似,可以用类名和关键字new来创建一个引用:ClassName::new。构造函数引用的思想就是将创建对象的行为传入到其他方法。
/**
* Supplier接口中抽象方法的签名为 () -> T
* 适用于无参构造函数
*/
Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get();
// 等价于
Supplier<Apple> c2 = () -> new Apple();
Apple a2 = c2.get();
/**
* Function接口中抽象方法apply的签名为 T -> R,T绑定Integer,返回R绑定Apple。
* 适用于参数为Integer的构造函数
*/
Function<Integer, Apple> c3 = Apple::new;
Apple a3 = c3.apply(110);
// 等价于
Function<Integer, Apple> c4 = (weight) -> new Apple(weight);
Apple a4 = c4.apply(110)
上面是直接调用抽象方法的例子(无参和单参,多参的类似,只需使用标签能匹配多参的接口),下面看一个将接口对象传入其他方法的例子。
// 创建一个List对象weights,保存待创建Apple对象的weight属性
List<Integer> weights = Arrays.asList(7,3,4,10);
public static List<Apple> map(List<Integer> list, Function<Integer, Apple> f) {
List<Apple> result = new ArrayList<>();
for (Integer e : list) {
result.add(f.apply(e));
}
return result;
}
/**
* 多个参数的Lambda表达式可以写成:
* (Integer weight, String color) -> new Apple(weight, color)
* 只需使用标签能匹配的接口
* 经过下面几步的简化,最后传入map的参数只需要为Apple::new即可
* 当创建Apple对象需要的参数不同时,需要改的只有map方法第二个参数的接口类型。
*
* 双参可使用BiFunction接口,签名为(T, U) -> R。
* T绑定String,U绑定Integer,R绑定Apple即可代表构造参数为color和weight
*
* 对于三个及以上的参数,Java没有预定义的接口,可自己创建,如:
* public interface TriFunction<T, U, V, R> {
* // TUV代表三个参数,R代表被创建的对象类型
* R apply(T t, U u, V v);
* }
*/
// 对于weights中的每一个重量都生成一个Apple对象,保存到apples中
Function<Integer, Apple> f = (Integer weight) -> new Apple(weight);
List<Apple> apples = map(weights, f);
// 简化后,直接将Lambda表达式作为参数传入map方法,省去了显式声明实现的具体接口
List<Apple> apples = map(weights, (Integer weight) -> new Apple(weight))
// 省去显式声明Lambda参数的类型
List<Apple> apples = map(weights, weight -> new Apple(weight))
// 使用构造函数引用简化后,最后只需要在map方法中将第二个参数写为Apple::new即可。
List<Apple> apples = map(weights, Apple::new);

被折叠的 条评论
为什么被折叠?



