Stream作为java8中增加的一个非常重要特性,为我们提供了对集合的一系列操作,简化了对集合元素的操作复杂度,让我们可以更加专注与业务逻辑的实现,stream的操作一般需要和Lambda表达式进行结合使用,达到简便的操作及清晰的代码。
Stream的使用主要分两部分:
- 惰性求值 (of, map, flatmap, filter…)
- 及早求值 (reduce, max, min, collect…)
判断一个操作是惰性求值还是及早求值可以通过操作的返回值进行判断,如果返回的是一个stream,那么就是惰性求值,否则就是及早求值。
惰性求值的特点:
- 惰性求值的操作并没有对集合元素进行实际的遍历,只有在进行及早求值的时候才开始遍历元素。
- 惰性求值返回的是stream对象。
在日常的使用中,我们都是惰性求值和及早求值进行配合,一般来说,先对集合进行惰性求值,最后再对生成的新stream对象进行及早求值。
大家可以把stream的操作过程想像成一个生产线,我们的集合元素从生产线的一头进入,然后在生产线的另一头重新生成集合或者元素,进入的过程就是生成一个steam对象,即第一次惰性求值,出来的时候是一个及早求值的过程,将steam重新生成集合或者元素,然后中间的所有操作都是惰性求值,中间的惰性求值过程相当于对生产线进行各种配置(排序,过滤,映射等等)。
下面的代码主要使用了stream的几个主要函数:
public class StreamTest {
public static void main(String[] args) {
StreamTest t = new StreamTest();
t.addOperation();
t.findMax();
t.getArtistInfo();
t.albumFilter();
t.albumFlatMap();
}
/**
* reduce操作
* 使用Lambda表达式和stream的reduce操作对int数组进行求和操作。
*/
private void addOperation() {
Integer[] list = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
//1. 将数组转换为列表。
int sum = Arrays.asList(list)
//2. 使用列表的stream方法获取stream流。
.stream()
//3. 使用stream的reduce函数遍历列表对象,并将对象相加。
//Lambda表达式“(x, y) -> x + y”为累加器。
.reduce(0, (x, y) -> x + y);
System.out.println("sum : " + sum);
}
/**
* max操作
* 使用stream的max操作求数组中的最大值。
*/
private void findMax() {
Integer[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int max = Arrays.asList(array)
.stream()//生成stream
.max((x, y) -> x - y)//及早求值运算,获取最大值
.get();//获取int值
System.out.println("max : " + max);
}
class Artist {
private String name;
private String nation;
private Artist(String name, String nation) {
this.name = name;
this.nation = nation;
}
}
/**
* map操作
* 接受艺术家列表作为参数,返回一个字符串列表,其中包含艺术家的 姓名和国籍
*/
private void getArtistInfo() {
Artist[] array = { new Artist("Bob", "China"), new Artist("Tom", "USA"), new Artist("Jerry", "Japan"),
new Artist("Tony", "England"), new Artist("Kitty", "China"), };
List artistInfo = Arrays.asList(array)
.stream()//生成stream
.map(artist -> artist.name + "-" + artist.nation)//对stream里面的元素进行映射
.collect(Collectors.toList());//重新生成集合
System.out.println("artistInfo : " + artistInfo);
}
class Album {
String name;
int length;
public Album(String name, int length) {
this.name = name;
this.length = length;
}
}
/**
* filter操作
* 接受专辑列表作为参数,返回一个由最多包含 3 首歌曲的专辑组成的列表。
*/
private void albumFilter() {
Album[] array = { new Album("aa", 3), new Album("bb", 5), new Album("cc", 6),
new Album("dd", 2), new Album("ee", 1) };
List list = Arrays.asList(array)
.stream()//生成stream
.filter(album -> album.length <= 3)//根据条件对元素进行过滤
.collect(Collectors.toList());//重新生成集合
//生成stream并进行遍历
list.stream().forEach(item -> System.out.print(item.name + " "));
System.out.println();
}
/**
* flatMap操作
* 接受专辑列表作为参数,返回一个由最多包含 3 首歌曲的专辑组成的列表。
*/
private void albumFlatMap() {
Album[] array1 = { new Album("aa", 3), new Album("bb", 5), new Album("cc", 6) };
Album[] array2 = { new Album("dd", 2), new Album("ee", 1) };
//1. 生成包含所有元素的一个stream流。
List list = Stream.of(array1, array2)
//Stream的flatMap方法可以将二维的数组或者列表扁平化,并将扁平化的数组或者列表转换为stream。
//2. 通过第一个操作生成的stream流,将每个数组元素转换为stream流,这里会调用两次
//albums的操作,对应两个Album数组。
.flatMap(albums -> Arrays.asList(albums).stream())
//3. 通过map函数对每个生成的stream的所有Album元素进行映射。
.map(item -> item.name + "-" + item.length)
//4. 重新生成集合。
.collect(Collectors.toList());
//需要注意的是对于2~4步,每一个Album数组都会单独走一遍,并且是按照第一步数组排列顺序进行的。
list.stream().forEach(album -> System.out.print(album + " "));
System.out.println();
}
}
对于flatMap和map操作的区别,举个例子: 假如我们需要将一包烟,按一根一根卖了换钱:
- 使用map只能一次处理一包烟。
- 使用flatMap可以一次处理多包烟。
关于这个问题可以结合前面的代码进行理解。