目录
1 collect()
1.1 collect(Collectors.toList())
1.2 collect(Collectors.joining(","))
2 map
3 filter
4 flatMap
5 max和min
6 reduce
7 sorted
8 match
该方法由Stream里的值生成一个列表,是一个及早求值操作。Stream的of方法使用一组初始值生成新的Stream:
List collected = Stream.of("a", "b", "c").collect(Collectors.toList());
assertEquals(Arrays.asList("a", "b", "c"), collected);
该方法用于将字符串进行拼接:
List integers = Arrays.asList("3", "2", "5", "1", "3", "12", "4", "21", "2");
String collect = integers.stream().collect(Collectors.joining(","));
如果有一个函数可以将一种类型的值转换成另外一种类型,map操作就可以使用该函数,将一个流中的值转换成一个新的流。
读者可能已经注意到,以前编程时或多或少使用过类似map的操作。比如编写一段Java代码,将一组字符串转换成对应的大写形式。在一个循环中,对每个字符串调用toUppercase方法,然后将得到的结果加入一个新的列表。代码如下所示:
List collected = new ArrayList<>();
for (String string : asList("a", "b", "hello")) {
String uppercaseString = string.toUpperCase();
collected.add(uppercaseString);
}
assertEquals(Arrays.asList("A", "B", "HELLO"), collected);
如果你经常实现上面代码中这样的for循环,就不难猜出map是Stream上最常用的操作之一。下面展示了如何使用新的流框架将一组字符串转换成大写形式:
List collected = Stream.of("a", "b", "hello").map(string -> string.toUpperCase()).collect(Collectors.toList());
assertEquals(Arrays.asList("A", "B", "HELLO"), collected);
传给map的Lambda表达式只接受一个String类型的参数,返回一个新的String。参数和返回值不必属于同一种类型,但是Lambda表达式必须是Function接口的一个实例,Function接口是只包含一个参数的普通函数接口。
遍历数据并检查其中的元素时,可尝试使用Stream中提供的新方法filter。
假设要找出一组字符串中以数字开头的字符串,比如字符串“1abc”和“abc”,其中“1abc”就是符合条件的字符串。可以使用一个for循环,内部用if条件语句判断字符串的第一个字符来解决这个问题,代码如下所示:
List beginningWithNumbers = new ArrayList<>();
for (String value : asList("a", "1abc", "abc1")) {
if (isDigit(value.charAt(0))) {
beginningWithNumbers.add(value);
}
}
assertEquals(Arrays.asList("1abc"), beginningWithNumbers);
你可能已经写过许多类似的代码:这被称为filter模式。该模式的核心思想是保留Stream中的一些元素,而过滤掉其他的。下面的代码展示了如何使用函数式风格编写相同的代码:
List beginningWithNumbers = Stream.of("a", "1abc", "abc1").filter(value -> isDigit(value.charAt(0))).collect(Collectors.toList());
assertEquals(Arrays.asList("1abc"), beginningWithNumbers);
和map很像,filter接受一个函数作为参数,该函数用Lambda表达式表示。该函数和前面示例中if条件判断语句的功能一样,如果字符串首字母为数字,则返回true。若要重构遗留代码,for循环中的if条件语句就是一个很强的信号,可用filter方法替代。
由于此方法和if条件语句的功能相同,因此其返回值肯定是true或者false。经过过滤,Stream中符合条件的,即Lambda表达式值为true的元素被保留下来。该Lambda表达式的函数接口正是之前文章中介绍过的Predicate。
flatMap方法可用Stream替换值,然后将多个Stream连接成一个Stream。
前面已介绍过map操作,它可用一个新的值代替Stream中的值。但有时,用户希望让map操作有点变化,生成一个新的Stream对象取而代之。用户通常不希望结果是一连串的流,此时flatMap最能派上用场。
我们看一个简单的例子。假设有一个包含多个列表的流,现在希望得到所有数字的序列。该问题的一个解法如下面所示:
List together = Stream.of(asList(1, 2), asList(3, 4)).flatMap(numbers -> numbers.stream()).collect(Collectors.toList());
assertEquals(Arrays.asList(1, 2, 3, 4), together);
调用stream方法,将每个列表转换成Stream对象,其余部分由flatMap方法处理。flatMap方法的相关函数接口和map方法的一样,都是Function接口,只是方法的返回值限定为Stream类型罢了。
Stream上常用的操作之一是求最大值和最小值。Stream API中的max和min操作足以解决这一问题。下面代码是查找专辑中最短曲目所用的代码,展示了如何使用max和min操作。为了方便检查程序结果是否正确,代码片段中罗列了专辑中的曲目信息:
List
查找Stream中的最大或最小元素,首先要考虑的是用什么作为排序的指标。已查找专辑中的最短曲目为例,排序的指标就是曲目的长度。
为了让Stream对象按照曲目长度进行排序,需要传给它一个Comparator对象。Java 8提供了一个新的静态方法comparing,使用它可以方便地实现一个比较器。放在以前,我们需要比较两个对象的某项属性的值,现在只需要提供一个存取方法就够了。本例中使用getLength方法。
此外,还可以调用空Stream的max方法,返回Optional对象。Optional对象有点陌生,它代表一个可能存在也可能不存在的值。如果Stream为空,那么该值不存在,如果不为空,则该值存在。通过调用get方法可以取出Optional对象中的值。
reduce操作可以实现从一组值中生成一个值。在上述例子中用到的max和min方法,因为常用而被纳入标准库中。事实上,这些方法都是reduce操作。
下面的代码展示了如何通过reduce操作对Stream中的数字求和的过程:
int count = Stream.of(1, 2, 3).reduce(0, (acc, element) -> acc + element);
assertEquals(6, count);
Lambda表达式就是reducer,它执行求和操作,有两个参数:传入Stream中的当前元素和acc。将两个参数相加,acc是累加器,保存着当前的累加结果。Lambda表达式的返回值是最新的acc,是上一轮acc的值和当前元素相加的结果。
sorted操作用来进行排序,对于对象类型进行排序的话,需要实现Comparable接口,或者指定指定比较器:
//自然排序
List integers = Arrays.asList(3, 2, 5, 1, 3, 12, 4, 21, 2);
integers.stream().sorted().forEach(System.out::println);
//指定比较器
List list = new ArrayList<>();
list.add(User.builder().name("Emma").age(24).email("[email protected]").build());
list.add(User.builder().name("Joyce").age(12).email("[email protected]").build());
list.add(User.builder().name("Wilson").age(28).email("[email protected]").build());
list.add(User.builder().name("Tom").age(45).email("[email protected]").build());
list.add(User.builder().name("Jerry").age(32).email("[email protected]").build());
list.stream().sorted(Comparator.comparingInt(User::getAge)).forEach(System.out::println);
List list = new ArrayList<>();
list.add(User.builder().name("Emma").age(24).email("[email protected]").build());
list.add(User.builder().name("Joyce").age(12).email("[email protected]").build());
list.add(User.builder().name("Wilson").age(28).email("[email protected]").build());
list.add(User.builder().name("Tom").age(45).email("[email protected]").build());
list.add(User.builder().name("Jerry").age(32).email("[email protected]").build());
//所有人是否都大于20岁
boolean b = list.stream().allMatch(item -> item.getAge() > 20);
System.out.println(b);
//是否有人大于20岁
boolean b1 = list.stream().anyMatch(item -> item.getAge() > 20);
System.out.println(b1);
//所有人都不是20岁
boolean b2 = list.stream().noneMatch(item -> item.getAge() == 20);
System.out.println(b2);