在上一次初步了解了Stream的创建和部分中间操作后,今天我们继续来了解Stream的其他中间操作,以及终止操作。
上一次我们提到了中间操作的筛选与切片,今天我们说一说映射、排序
映射:
假如我们现在有这样一个数组:
List<String> list=Arrays.asList("aaa","bbb","ccc","ddd","eee");
这是一组含有小写字母的集合,我们想将该数组中的每个元素转换为大写并且输出打印,按照传统的方法,我们可能需要遍历这个数组,然后使用toUpperCase()方法进行逐一转化并且打印。而如果我们使用stream的映射,只需要这样:
list.stream().map((str)->str.toUpperCase()).forEach(System.out::println);
运行结果:
提到映射,大家应该会想到Map集合,键值对形式的集合,键与值之间存在映射关系,而stream中的映射,关键字就是map,我们查看一下map()方法的参数列表:
不难看出map需要一个函数型接口作为参数,即提供一个值,返回一个值,所以map()的工作原理我们可以理解为:将某个元素通过我们想执行的操作后,形成一个新的元素,而我们想执行的操作,便是map()中的函数式接口,比如上面的例子,我们想将list集合中的每个元素,都转换成大写,于是调用函数式接口对list中每个元素进行转换大写操作,最后使用终止操作进行输出打印。
而如果有这样一个方法:
public static Stream<Character> filterCharacter(String str){
List<Character> list=new ArrayList<>();
for (Character ch:
str.toCharArray()) {
list.add(ch);
}
return list.stream();
}
这是一个用于将字符串中的每个字符拆分成单个字符的方法,返回一个类型为Character的Stream
同样是上面的list集合,现在我们需要将list集合中的每个元素拆分成单字符输出。 同样使用类似上面的方法:
list.stream().map(StreamAPI2::filterCharacter);
这样得到的是一个Stream,但该Stream是一个什么类型呢?
由于filterCharacter方法的返回值为一个类型为Character的Stream,所以这里用于接收的Stream类型应该是如此:
Stream<Stream<Character>> stream=list.stream().map(StreamAPI2::filterCharacter);
一个类型为Stream的Stream,而当我们使用forEach输出时,该怎么写?
我们先看看forEach()的参数列表
是一个消费型接口,即没有返回值,有一个参数,而提示也很明显告诉我们了,需要传入一个类型为Character的Stream,所以我们应该这样写:
stream.forEach(sm->{
sm.forEach(System.out::println;
})
由于我们需要进行终止操作的流类型较为特殊,所以我们需要这样重复进行操作,略显麻烦,因此我们还可以使用映射的另外一种方式:flatmap进行操作。
flatmap,同样是接收一个函数作为参数,但它是将流中的每个值都转换成一个流,最后将这些流连接成一个流,在上述的例子中,我们就可以这样直接写:
Stream<Character> stream2=list.stream().flatMap(StreamAPI2::filterCharacter);
stream2.forEach(System.out::println);
在map方法中,是将list集合中每个元素进行拆分,然后形成一个新的元素,而在flatmap方法中,是将list集合中的每个元素中的每个值都拆分成了一个流,执行完毕后将这些流连接成一个,为了方便理解,我用这样的两张图张图分别表示两种方法的区别:
map方法:
flatmap方法:
这两者有一点类似list集合中的add方法和addAll方法。
接下来我们说一说排序,排序有自然排序以及定制排序两种
自然排序,使用的是Comparable接口中的方法进行排序,这里不多说,我们举两个例子:
List<String> list=Arrays.asList("ddd","aaa","ccc","bbb","eee");
List<Integer> list2=Arrays.asList(2,1,3,5,6,4,9);
list.stream().sorted().forEach(System.out::println);
list2.stream().sorted().forEach(System.out::println);
运行结果:
不难看出该排序将第一个字母集合按照字母顺序进行了排序,也将第二个数字集合按照了从小到大的顺序进行了排序。
我们主要说一说定制排序,定制排序指的是按照我们自定义的排序规则进行排序
不难看出定制排序需要一个comparator接口作为参数,这是一个需要两个类型相同的参数,返回值为int的函数式接口。
而有这样一个集合:
List<Student> stus= Arrays.asList(
new Student(001,"张三",16,168.25),
new Student(002,"李四",48,178.25),
new Student(007,"赵六",18,180.68),
new Student(003,"王五",12,148.45)
);
我们需要将该集合中的对象按年龄从小到大进行排序,若年龄相同,则按姓名排序,最后将结果进行输出打印:
@Test
public void test7(){
stus.stream().sorted((e1,e2)->{
if (e1.getAge().equals(e2.getAge())){
return e1.getName().compareTo(e2.getName());
}else {
return e1.getAge().compareTo(e2.getAge());
}
}).forEach(System.out::println);
}