共计 4583 个字符,预计需要花费 12 分钟才能阅读完成。
第三章 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 表达式,也就是一个 Compatoreg:
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 的参 > 数里面给的那个函数,然后再把函数本身用于结果。