Java8实战-总结12

Java8实战-总结12

  • Lambda表达式
    • Lambda 和方法引用实战
      • 第1步:传递代码
      • 第2步:使用匿名类
      • 第3步:使用Lambda表达式
      • 第4步:使用方法引用
    • 复合Lambda表达式的有用方法
      • 比较器复合
        • 逆序
        • 比较器链
      • 函数复合

Lambda表达式

Lambda 和方法引用实战

为了给所有关于Lambda的内容收个尾,需要继续研究开始的那个问题——用不同的排序策略给一个Apple列表排序,并需要展示如何把一个原始粗暴的解决方案转变得更为简明。这会用到迄今讲到的所有概念和功能:行为参数化、匿名类、Lambda表达式和方法引用。想要实现的最终解决方案是这样的:

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

第1步:传递代码

Java 8API已经提供了一个List可用的sort方法,不用自己去实现它。最困难的部分已经搞定了。但是,如何把排序策略传递给sort方法?sort方法的签名是这样的:

void sort (Comparator<? super E> c)

它需要一个Comparator对象来比较两个Apple。这就是在Java中传递策略的方式:它们必须包裹在一个对象里。sort的行为被参数化了:传递给它的排序策略不同,其行为也会不同。

第一个解决方案看上去是这样的:

	public class AppleComparator implements Comparator<Apple> {
		public int compare(Apple a1, Apple a2) {
			return a1.getWeight().compareTo(a2.getWeight());
		}
	}
	
	inventory.sort(new AppleComparator());

第2步:使用匿名类

在前面看到了,可以使用匿名类来改进解决方案,而不是实现一个Comparator却只实例化一次:

	inventory.sort(new Comparator<Apple>() {
		public int compare(Apple al, Apple a2) {
			return a1.getWeight().compareTo(a2.getWeight ());
		}
	});

第3步:使用Lambda表达式

但解决方案仍然挺啰嗦的。Java 8引入了Lambda表达式,它提供了一种轻量级语法来实现相同的目标:传递代码。在需要函数式接口的地方可以使用Lambda表达式。回顾一下:函数式接口就是仅仅定义一个抽象方法的接口。抽象方法的签名(称为函数描述符)描述了Lambda表达式的签名。在这个例子里,Comparator代表了函数描述符(T, T) -> int。因为你用的是苹果,所以它具体代表的就是(Apple, Apple) -> int。改进后的新解决方案看上去就是这样的:

inventory.sort((Apple a1,Apple a2) -> a1.getWeight().compareTo(a2.getWeight())
);

前面解释过了,Java编译器可以根据Lambda出现的上下文来推断Lambda表达式参数的类型。那么解决方案就可以重写成这样:

inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));

代码还能变得更易读一点吗?Comparator具有一个叫作comparing的静态辅助方法,它可以接受一个Function来提取Comparable键值,并生成一个Comparator对象。它可以像下面这样用(注意现在传递的Lambda只有一个参数:Lambda说明了如何从苹果中提取需要比较的键值):

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

现在可以把代码再改得紧凑一点了:

import static java.util.Comparator.comparing;inventory.sort(comparing((a) -> a.getWeight()));

第4步:使用方法引用

前面解释过,方法引用就是替代那些转发参数的Lambda表达式的语法糖。可以用方法引用让代码更简洁(假设静态导入了java.util.Comparator.comparing):

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

这就是你的最终解决方案。这比Java 8之前的代码好在哪儿呢?它比较短;它的意思也很明显,并且代码读起来和问题描述差不多:“对库存进行排序,比较苹果的重量。”

复合Lambda表达式的有用方法

Java 8的好几个函数式接口都有为方便而设计的方法。具体而言,许多函数式接口,比如用于传递Lambda表达式的Comparator、Function和Predicate都提供了允许你进行复合的方法。这是什么意思呢?在实践中,这意味着你可以把多个简单的Lambda复合成复杂的表达式。比如,你可以让两个谓词之间做一个or操作,组合成一个更大的谓词。而且,你还可以让一个函数的结果成为另一个函数的输入。你可能会想,函数式接口中怎么可能有更多的方法呢?(毕竞,这违背了函数式接口的定义啊!)窍门在于,我们即将介绍的方法都是默认方法,也就是说它们不是抽象方法。我们会在第9章详谈。现在只需相信我们,等想要进一步了解默认方法以及你可以用它做什么时,再去看看第9章。

比较器复合

前面看到,可以使用静态方法Comparator.comparing,根据提取用于比较的键值的Function来返回一个Comparator,如下所示:

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

逆序

如果想要对苹果按重量递减排序怎么办?用不着去建立另一个Comparator的实例。接口有一个默认方法reversed可以使给定的比较器逆序。因此仍然用开始的那个比较器,只要修改一下前一个例子就可以对苹果按重量递减排序:

//按重量递减排序
inventory.sort(comparing(Apple::getWeight).reversed());

比较器链

上面说得都很好,但如果发现有两个苹果一样重怎么办?哪个苹果应该排在前面呢?可能需要再提供一个Comparator来进一步定义这个比较。比如,在按重量比较两个苹果之后,可能想要按原产国排序。thenComparing方法就是做这个用的。它接受一个函数作为参数(就像comparing方法一样),如果两个对象用第一个Comparator比较之后是一样的,就提供第二个Comparator。又可以优雅地解决这个问题了:

//按重量递减排序
inventory.sort (comparing(Apple::getWeight)
		.reversed()
		.thenComparing(Apple::getCountry));
两个苹果一样重时,进一步按国家排序

##谓词复合
谓词接口包括三个方法:negateandor,可以重用已有的Predicate来创建更复杂的谓词。比如,可以使用negate方法来返回一个Predicate的非,比如苹果不是红的: 产生现有

PredicatePredicatecApple> notRedApple = redApple.negate();

对象redApple想要把两个Lambdaand方法组合起来,比如一个苹果既是红色又比较重:
链接两个谓词来生成另

Predicate<Apple> redAndHeavyApple =一个predicate对象redApple.and(a -> a.getWeight()> 150);

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

//链接Predicate的方法来构造更复杂Predicate对象
Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(a -> a.getWeight() >  150).or(a ->"green".equals(a.getColor()));

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

函数复合

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

andThen方法会返回一个函数,它先对输入应用一个给定函数,再对输出应用另一个函数。比如,假设有一个函数f给数字加1 (x -> x +1),另一个函数g给数字乘2,可以将它们组合成一个函数h,先给数字加1,再给结果乘2:

//数学上会写作g(f(x))或(g o f)(x)
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g);
//这将返回4
int result = h.apply(1);

也可以类似地使用compose方法,先把给定的函数用作compose的参数里面给的那个函数,然后再把函数本身用于结果。比如在上一个例子里用compose的话,它将意味着f(g(x)),而andThen则意味着g(f(x)):

Function<Integer, Integer> f = x -> x + 1;
Function<Integer,Integer> g = x -> x * 2;
//数学上会写作f(g(x))或(f o g)(x)
Function<Integer, Integer> h = f.compose(g);
//这将返回3
int result = h.apply(1);

下图说明了andThencompose之间的区别。
Java8实战-总结12_第1张图片
这一切听起来有点太抽象了。那么在实际中这有什么用呢?比方说有一系列工具方法,对
String表示的一封信做文本转换:

public class Letter {
	public static String addHeader(String text) {
		return "From Raoul, Mario and Alan:" + text;
	}
public static String addFooter(String text) {
	return text + "Kind regards";
	
public static String checkspelling(String text) {
	return text.replaceAll("labda","lambda");

现在以通过复合这些工具方法来创建各种转型流水线了,比如创建一个流水线:先加上抬头,然后进行拼写检查,最后加上一个落款,如下图所示:

Punction<String, String> addHeader = Letter::addHeader;
Function<String, String> transformationPipeline= addHeader.andThen(Letter::checkspelling).andThen(Letter::addFooter);

Java8实战-总结12_第2张图片

第二个流水线可能只加抬头、落款,而不做拼写检查:

Function<String, String> addHeader = Letter::addHeader;
Function<String, String> transformationPipeline= addHeader.andThen(Letter::addFooter);

s

你可能感兴趣的:(python,windows,开发语言)