第三章 Lambda表达式

函数式接口

函数式接口就是只定义一个抽象方法的接口,哪怕有很多默认方法,只要接口只定义了一个抽象方法,它就仍然是一个函数式接口。

常用函数式接口

图片描述
图片描述

函数描述符

函数式接口的抽象方法的签名称为函数描述符。

在哪里可以使用Lambda?

只有在需要函数式接口的时候才可以传递Lambda
下哪些是使用Lambda表达式的有效方式?
(1)

execute(() -> {});
public void execute(Runnable r){ 
r.run(); 
} 

(2)

return () -> "Tricky example ;-)"; 

(3)

Predicate<Apple> p = (Apple a) -> a.getWeight();

答案:只有1和2是有效的
第一个例子有效,是因为Lambda() -> {}具有签名() -> void,这和Runnable中的
抽象方法run的签名相匹配。请注意,此代码运行后什么都不会做,因为Lambda是空的!
第二个例子也是有效的。事实上,fetch方法的返回类型是Callable<String>。
Callable<String>基本上就定义了一个方法,签名是() -> String,其中T被String代替
了。因为Lambda() -> "Trickyexample;-)"的签名是() -> String,所以在这个上下文
中可以使用Lambda。
第三个例子无效,因为Lambda表达式(Apple a) -> a.getWeight()的签名是(Apple) ->
Integer,这和Predicate<Apple>:(Apple) -> boolean中定义的test方法的签名不同。

@FunctionalInterface又是怎么回事

这个标注用于表示该接口会设计成一个函数式接口,@FunctionalInterface不是必需的,它就像是@Override标注表示方法被重写了。

Java 7中的带资源的try语句

它已经简化了代码,因为你不需要显式地关闭资源了.

public static String processFile() throws IOException { 
  try (BufferedReader br = 
  new BufferedReader(new FileReader("data.txt"))) { 
  return br.readLine(); 
  } 
} 

函数式接口:Predicate断言

java.util.function.Predicate<T>接口定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean。

函数式接口:Consumer

java.util.function.Consumer<T>定义了一个名叫accept的抽象方法,它接受泛型T的对象,没有返回(void)。

函数式接口:Function

java.util.function.Function<T, R>接口定义了一个叫作apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象。
eg:

@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; 
} 
// [7, 2, 6] 
List<Integer> l = map( 
 Arrays.asList("lambdas","in","action"), 
 (String s) -> s.length() 
 ); 

避免自动装箱、拆箱

一般来说,针对专门的输入参数类型的函数式接口的名称都要加上对应的原始类型前缀,比如DoublePredicate、IntConsumer、LongBinaryOperator、IntFunction等。Function接口还有针对输出参数类型的变种:ToIntFunction<T>、IntToDoubleFunction等。

关于异常

请注意,任何库中的函数式接口都不允许抛出受检异常(checked exception)。如果你需要Lambda表达式来抛出异常,有两种办法:定义一个自己的函数式接口,并声明受检异常,或者把Lambda包在一个try/catch块中。

目标类型

Lambda表达式需要的类型称为目标类型。(即对应的函数式接口)

类型推断

你还可以进一步简化你的代码。Java编译器会从上下文(目标类型)推断出用什么函数式接
口来配合Lambda表达式,这意味着它也可以推断出适合Lambda的签名,因为函数描述符可以通
过目标类型来得到。这样做的好处在于,编译器可以了解Lambda表达式的参数类型,这样就可
以在Lambda语法中省去标注参数类型。换句话说,Java编译器会像下面这样推断Lambda的参数
类型:

List<Apple> greenApples = 
filter(inventory, a -> "green".equals(a.getColor())); 

方法引用

为三种不同类型的Lambda表达式构建方法引用的办法:
图片描述
List<String> str = Arrays.asList("a","b","A","B"); 
str.sort((s1, s2) -> s1.compareToIgnoreCase(s2)); 

Lambda表达式的签名与Comparator的函数描述符兼容。利用前面所述的方法,这个例子可
以用方法引用改写成下面的样子:

List<String> str = Arrays.asList("a","b","A","B"); 
str.sort(String::compareToIgnoreCase); 

构造函数引用

对于一个现有构造函数,你可以利用它的名称和关键字new来创建它的一个引用:
ClassName::new。

构造函数引用
要怎么样才能对具有三个参数的构造函数,比如Color(int, int, int),使用构造函数引用呢?
答案:你看,构造函数引用的语法是ClassName::new,那么在这个例子里面就是Color::new。但是你需要与构造函数引用的签名匹配的函数式接口。但是语言本身并没有提供这样的函数式接口,你可以自己创建一个:

public interface TriFunction<T, U, V, R>{ 
 R apply(T t, U u, V v); 
} 

现在你可以像下面这样使用构造函数引用了:

TriFunction<Integer, Integer, Integer, Color> colorFactory = Color::new; 

Comparator 类内部comparing实现

comparing 方法一
查看 Comparator 类内部实现,还有一个 comparing 方法,实现如下,

   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));
   }

其返回值是 (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2)); 一个lambda表达式,也就是一个Compator
eg:

Comparator<Apple> c = Comparator.comparing(Apple::getWeight); 

comparing 方法二

   public static <T, U> Comparator<T> comparing(
           Function<? super T, ? extends U> keyExtractor,
           Comparator<? super U> keyComparator)
   {
       Objects.requireNonNull(keyExtractor);
       Objects.requireNonNull(keyComparator);
       return (Comparator<T> & Serializable)
           (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
                                             keyExtractor.apply(c2));
   }

和comparing 方法一不同的是 该方法多了一个参数 keyComparator ,keyComparator 是创建一个自定义的比较器。

Collections.sort(employees, Comparator.comparing(
               Employee::getName, (s1, s2) -> {
                   return s2.compareTo(s1);
               }));

比较器复合

逆序

inventory.sort(comparing(Apple::getWeight).reversed()); 

比较器链
thenComparing方法就是做这个用的

inventory.sort(comparing(Apple::getWeight) 
.reversed().thenComparing(Apple::getCountry)); 

谓词复合(断言复合)

谓词接口包括三个方法:negate、and和or,让你可以重用已有的Predicate来创建更复
杂的谓词。比如,你可以使用negate方法来返回一个Predicate的非,比如苹果不是红的:

Predicate<Apple> notRedApple = redApple.negate(); 

你可能想要把两个Lambda用and方法组合起来,比如一个苹果既是红色又比较重:

Predicate<Apple> redAndHeavyApple = 
redApple.and(a -> a.getWeight() > 150);

你可以进一步组合谓词,表达要么是重(150克以上)的红苹果,要么是绿苹果:

Predicate<Apple> redAndHeavyAppleOrGreen = 
redApple.and(a -> a.getWeight() > 150) 
.or(a -> "green".equals(a.getColor())); 

这一点为什么很好呢?从简单Lambda表达式出发,你可以构建更复杂的表达式,但读起来仍然和问题的陈述差不多!请注意,and和or方法是按照在表达式链中的位置,从左向右确定优先级的。因此,a.or(b).and(c)可以看作(a || b) && c。

函数复合

你还可以把Function接口所代表的Lambda表达式复合起来。Function接口为此配了andThen和compose两个默认方法,它们都会返回Function的一个实例。

  • andThen方法会返回一个函数,它先对输入应用一个给定函数,再对输出应用另一个函数;用compose方法,先把给定的函数用作compose的参>数里面给的那个函数,然后再把函数本身用于结果。

小白龙
56 声望6 粉丝