5.4 查找和匹配
另一个常见的数据处理套路是看看数据集中的某些元素是否匹配一个给定的属性。Stream API通过allMatch、anyMatch、noneMatch、findFirst和findAny方法提供了这样的工具。
5.4.1 检查谓词是否至少匹配一个元素
anyMatch方法可以回答“流中是否有一个元素能匹配给定的谓词”。比如,你可以用它来看看菜单里面是否有素食可选择:
if(menu.stream().anyMatch(Dish::isVegetarian)){System.out.println("The menu is (somewhat) vegetarian friendly!!");}
anyMatch方法返回一个boolean,因此是一个终端操作。
5.4.2 检查谓词是否匹配所有元素
allMatch方法的工作原理和anyMatch类似,但它会看看流中的元素是否都能匹配给定的谓词。比如,你可以用它来看看菜品是否有利健康(即所有菜的热量都低于1000卡路里):
boolean isHealthy = menu.stream().allMatch(dish -> dish.getCalories() < 1000);
noneMatch
和allMatch相对的是noneMatch。它可以确保流中没有任何元素与给定的谓词匹配。比如,你可以用noneMatch重写前面的例子:
boolean isHealthy = menu.stream().noneMatch(dish -> dish.getCalories() >= 1000);
anyMatch、allMatch和noneMatch这三个操作都用到了所谓的短路,这就是大家熟悉的Java中&&和||运算符短路在流中的版本。
短路求值
有些操作不需要处理整个流就能得到结果。例如,假设你需要对一个用
and连起来的大布尔表达式求值。不管表达式有多长,你只需找到一个表达式为false,就可以推断整个表达式将返回false,所以用不着计算整个表达式。这就是短路。对于流而言,某些操作(例如
allMatch、anyMatch、noneMatch、findFirst和findAny)不用处理整个流就能得到结果。只要找到一个元素,就可以有结果了。同样,limit也是一个短路操作:它只需要创建一个给定大小的流,而用不着处理流中所有的元素。在碰到无限大小的流的时候,这种操作就有用了:它们可以把无限流变成有限流。5.7节会介绍无限流的例子。
5.4.3 查找元素
findAny方法将返回当前流中的任意元素。它可以与其他流操作结合使用。比如,你可能想找到一道素食菜肴。可以结合使用filter和findAny方法来实现这个查询:
Optional<Dish> dish =menu.stream().filter(Dish::isVegetarian).findAny();
流水线将在后台进行优化使其只需走一遍,并在利用短路找到结果时立即结束。不过稍等一下,代码里面的Optional是个什么玩意儿?
Optional简介
Optional类(java.util.Optional)是一个容器类,代表一个值存在或不存在。在上面的代码中,findAny可能什么元素都没找到。Java 8的库设计人员引入了Optional,这样就不用返回众所周知容易出问题的null了。这里不会详细讨论Optional,因为第11章会详细解释你的代码如何利用Optional,避免和null检查相关的bug。不过现在,了解一下Optional里面几种可以迫使你显式地检查值是否存在或处理值不存在的情形的方法也不错。
isPresent()将在Optional包含值的时候返回true,否则返回false。ifPresent(Consumer会在值存在的时候执行给定的代码块。第3章介绍过block) Consumer函数式接口,它让你传递一个接受T类型参数,并返回void的Lambda表达式。T get()会在值存在时返回值,否则抛出一个NoSuchElement异常。T orElse(T other)会在值存在时返回值,否则返回一个默认值。
例如,在前面的代码中你需要显式地检查Optional对象中是否存在一道菜可以访问其名称:
menu.stream().filter(Dish::isVegetarian).findAny() ←---- 返回一个Optional<Dish>.ifPresent(dish -> System.out.println(dish.getName()); ←---- 如果包含一个值就打印它,否则什么都不做
5.4.4 查找第一个元素
有些流由一个出现顺序(encounter order)来指定流中项目出现的逻辑顺序(比如由List或排序好的数据列生成的流)。对于这种流,你可能想要找到第一个元素。为此有一个findFirst方法,它的工作方式类似于findAny。例如,给定一个数字列表,下面的代码能找出第一个平方能被3整除的数:
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);Optional<Integer> firstSquareDivisibleByThree =someNumbers.stream().map(n -> n * n).filter(n -> n % 3 == 0).findFirst(); // 9
何时使用
findFirst和findAny你可能会想,为什么会同时有
findFirst和findAny呢?答案是并行。找到第一个元素在并行上限制更多。如果你不关心返回的元素是哪个,请使用findAny,因为它在使用并行流时限制较少。
