Lambda表达式
Lambda表达式,可以简洁地表示一个行为或传递代码。
3.1 Lambda管中窥豹
Lambda表达式有三个部分,参数列表,箭头,Lambda主体。
Lambda没有return语句,因为已经隐含了return。
基本语法是 : (parameters) -> expression (parameters) -> {statements;}
3.2 在哪里以及如何使用Lambda
可以在函数式接口上使用Lambda表达式。
3.2.1 函数式接口
函数式接口就是只定义一个抽象方法的接口。哪怕有很多默认方法,只要接口只定义了一个抽象方法,它就仍然是一个函数式接口。
Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。
Runnable r1 = () -> System.out.println("Hello World 1");
Runnable r2 = new Runnable() {
public void run() {
System.out.println("Hello World 2");
}
};
public static void process(Runnable r) {
r.run();
}
process(r1);
process(r2);
process(() -> System.out.println("Hello World 3"));
3.2.2 函数描述符
函数式接口的抽象方法的签名基本上就是Lambda表达式的签名,将这种抽象方法叫做函数描述符。 @FunctionalInterface标注该接口为一个函数式接口
3.3把Lambda付诸实践:环绕执行模式
资源处理时一个常见的模式就是打开一个资源,做一些处理,然后关闭资源。这个设置和清理阶段总是很类似,并且会围绕着执行处理的那些重要代码。
3.3.1 第1步:记得行为参数化
String result = processFile((BufferedReader br) -> br.readLine() + br.readLine());
3.3.2 第2步:使用函数式接口来传递行为
创建一个能匹配BufferedReader -> String,还可以抛出IOException异常的接口。
3.3.3 第3步:执行一个行为
需要一种方法在processFile主体内执行Lambda所代表的代码。
public static String processFile(BufferedReaderProcessor p) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
return p.process(br);
}
}
3.3.4 第4步: 传递Lambda
String twoLines = processFile((BufferedReader br -> br.readLine() + br.readLine()));
3.4 使用函数式接口
介绍Predicate、Consumer和Function
3.4.1 Predicate
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
public static <T> List<T> filter(List<T> list, Predicate<T> p) {
List<T> results = new ArrayList<>();
for(T t : list) {
if(p.test(t)) {
results.add(t);
}
}
return results;
}
Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List<String> nonEmpty = filter(listOfStrings,nonEmptyStringPredicate);
3.4.2 Consumer
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
public static <T> void forEach(List<T> list, Consumer<T> c) {
for (T t : list) {
c.accept(t);
}
}
forEach(Arrays.asList(1,2,3,4,5),(Integer i) -> System.out.println(i));
3.4.3 Function
@FunctionalInterface
public interface Function<T,R>{
R.apply(T t);
}
public static <T, R> List<R> map(List<T> list, Function<T,R> f) {
List<R> result = new ArrayList<>();
for (T s : list) {
result.add(f.apply(s));
}
return result;
}
List <Integer> result = map(Arrays.asList("lambdas","in","action"),(String s) -> s.length());
任何函数式接口都不允许抛出受检异常,如果需要Lambda表达式来抛出异常,一个是定义一个自己的函数式接口,并声明受检异常,或者把Lambda包在一个try/catch块中。
3.5 类型检查、类型推断以及限制
3.5.1 类型检查
Lambda的类型是从使用Lambda的上下文推断出来的。具体是根据入参的函数式接口,从接口的入参和出参进行匹配。
3.5.2 同样的Lambda,不同的函数式接口
有了目标类型的概念,同一个Lambda表达式就可以与不同的函数式接口联系起来,只要抽象方法的签名能够兼容。
3.5.3 类型推断
编译器可以了解Lambda表达式的参数类型,这样就可以在Lambda语法中省去标注参数类型。
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
3.5.4 使用局部变量
Lambda表达式只能捕获指派给它们的局部变量一次。第一Java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量。第二不鼓励使用改变外部变量的典型命令式编程模式。
方法引用
方法引用可以重复使用现有的方法定义,并像Lambda一样传递。
3.6.1 管中窥豹
方法引用主要用静态方法的方法引用、指向任意类型实例方法的方法引用、指向现有对象的实例方法的方法引用。编译器会进行一种与Lambda表达式类似的类型检查过程,来确定对于给定的函数式接口,方法引用的签名必须和上下文类型匹配。
3.6.2 构造函数引用
Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get();
Function<Integer,Apple> c2 = Apple:new;
Apple a2 = c2.apply(110);
3.7 Lambda和方法引用实战
用不同的排序策略给一个Apple列表排序
3.7.1 第1步:传递代码
void sort(Comparator<? super E> c)
public class AppleComparator implements Comparator<Apple> {
public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight());
}
}
inventory.sort(new AppleComparator());
3.7.2 第2步:使用匿名类
inventory.sort(new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
});
3.7.3 第3步:使用Lambda表达式
inventory.sort((a1, a2) -> a1.getWeight().CompareTO(a2.getWeight()));
进一步
inventory.sort(Comparator.comparing((a) -> a.getWeight()));
3.7.4 第4步:使用方法引用
inventory.sort(Comparator.comparing(Apple::getWeight));
3.8 复合Lambda表达式的有用方法
可以把多个简单的Lambda复合成复杂的表达式。
3.8.1 比较器复合
逆序
inventory.sort(Comparator.comparing(Apple::getWeight).reversed());
比较器链
inventory.sort(Comparator.comparing(Apple::getWeight).reversed().thenComparing(Apple::getCountry));
3.8.2 谓词复合
谓词接口包括negate、and和or
Predicate<Apple> notRedApple = redApple.negate();
Predicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150);
Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(a -> a.getWeight() > 150).or(a -> "green".equals(a.getColor()));
3.8.3 函数复合
Function配了andThen和compose两个默认方法来复合表达式。
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g);
Function<Integer, Integer> i = f.compose(g);
3.9 小结
Lambda表达式可以理解为一种匿名函数:它没有名称,但有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常的列表。
Lambda表达式让你可以简洁地传递代码。
函数式接口就是仅仅声明了一个抽象方法的接口。
只有在接受函数式接口的地方才可以使用Lambda表达式。
Lambda表达式允许你直接内联,为函数式接口的抽象方法提供实现,并且将整个表达式作为函数式接口的一个实例。
Java 8自带一些常用的函数式接口,放在java.util.function包里,包括Predicate、Function<T,R>、Supplier、Consumer和BinaryOperator。
为了避免装箱操作,对Predicate和Function<T, R>等通用函数式接口的原始类型特化:IntPredicate、IntToLongFunction等
环绕执行模式(即在方法所必需的代码中间,你需要执行点儿什么操作,比如资源分配和清理)可以配合Lambda提高灵活性和可重用性。
Lambda表达式所需要代表的类型称为目标类型。
方法引用让你重复使用现有的方法实现并直接传递它们。
Comparator、Predicate和Function等函数式接口都有几个可以用来结合Lambda表达式的默认方法。