你好,Stream流操作

内部迭代到外部迭代

外部迭代

Java在使用集合类的时候,通常是在集合上进行迭代,然后处理每个元素,比如下面这种情况:

有一个Person类,有这个人名字和工作两种属性。

public class Person {
    private String name;
    private String work;
}

有一个列表中包含了一些人的信息,想要计算工作类型是医生的人数有多少,通常我们会这么写:

int count = 0;
for (Person person : list) {
    if(person.getWork().equals("Doctor")){
        count++;
    }
}

虽然这么操作没什么问题,但是如果后续需求变更,每次迭代会重复很多很多类型这样的代码。其实for循环就是一个封装了迭代的语法糖。显示调用iterator方法,产生一个新的iterator对象,来控制整个迭代过程,这就是外部迭代。迭代器通过显示的调用iterator对象中的hasNext()方法和next()方法完成迭代,其实本质也是一种串行化操作。

int count = 0;
Iterator<Person> iterator = list.iterator();
while (iterator.hasNext()) {
    Person person = iterator.next();
    if (person.getWork().equals("Doctor")) {
        count++;
    }
}

内部迭代

需要注意的是,stream()方法的调用,跟调用iterator()作用是一样的,但这个方法不是返回控制迭代的Iterator对象,而是返回内部迭代中对应的接口:Stream

long count = list.stream()
                .filter(person -> person.getWork().equals("Doctor"))
                .count();

这样的操作主要分解为两步简单的操作:

  • 找出所有工作是医生的人。
  • 计算人数。

每一种操作都是对应Stream接口中的方法。首选根据条件对集合种的对象进行过滤:filter,只保留通过测试的对象。测试通过一个函数来完成,过滤工作是医生的对象,返回truefalse。根据Stream API的函数式编程风格,并没有改变集合的内容,而且描述出了Stream中的内容。count()方法计算给定Steam里包含了多少个对象。

实现机制

list.stream().filter(person -> person.getWork().equals("Doctor"));

我们来看这行代码,实际它什么都没有做。一般的情况下,我们在Java中调用方法,计算机会立即进行操作,比如System.out.println("笑凡尘");会打印出笑凡尘在控制台上。但是Stream是不同的,返回的对象不是一个新的集合,而是创建集合的一种描述。因为此时filter只是描述了刻画出了Stream,并没有产生新的集合。类似这种只描述Stream而不产生新集合的方法叫做惰性求值方法。类型count()这种会从Stream中产生值的方法叫做及早求值法

惰性求值法

在上面的代码中我们稍作一些改动,比如打印一下每个人的名字,因为是惰性求值,所以这里根本不会打印出名字:

list.stream().filter(person -> {
    System.out.println(person.getName());
    return person.getWork().equals("Doctor");
});

及早求值法

我们调用一下count()方法,就会打印出每个人的名字。

list.stream().filter(person -> {
    System.out.println(person.getName());
    return person.getWork().equals("Doctor");
}).count();

判断惰性/及早求值

看它的返回值,如果返回值是Stream那么就是惰性求值,如果返回另外一个值或者是空,那就是及早求值。

常用流操作

collect(toList())

collect(toList())方法是Stream中的值生成一个列表,及早求值操作。

我们拿新建一个集合来举例:

传统方法:

List<String> list = new ArrayList<>();
list.add("笑凡尘");
list.add("佟丽娅");
list.add("佩奇");

流操作:

List<String> list = Stream.of("笑凡尘", "佟丽娅", "佩奇")
    						.collect(Collectors.toList());

map

如果有一个函数可以将一种类型的值转换成另外一种类型,map操作就可以使用该函数,将一个流中的值转换成一个新的流。

例如list集合中的字母转换成大写:["a", "b", "c"]

传统方法:

List<String> result = new ArrayList<>();
for (String str : list) {
    String upperStr = str.toUpperCase();
    result.add(upperStr);
}

流操作:

List<String> result = list.stream().map(str -> str.toUpperCase())
    								.collect(Collectors.toList());

filter

遍历数据并检查元素时,可以使用filter。

例如在集合list中找出包含adc字符串的符合条件的字符串:["abosc", "apcadc", "apwedc", "adcadc"]

传统方法:

List<String> result = new ArrayList<>();
for (String str : list) {
    if(str.contains("adc")){
        result.add(str);
    }
}

流操作:

List<String> result2 = list.stream().filter(str -> str.contains("adc"))
    								.collect(Collectors.toList());

flatMap

flatMap方法可用Stream替换值,然后将多个Stream连接成一个Stream。

假设现在有两个集合list1和list2,分别如下[1, 2],[3, 4]

List<Integer> result = Stream.of(list1, list2)
    .flatMap(num -> num.stream())
    .collect(Collectors.toList());

这样在输出新生成的集合为:

[1, 2, 3, 4]

max和min

假设有一个字符串集合如下:[“aaaa”, "b", "asf", "aa"]

找出字符串长度最小的值:

String result = list.stream().min(Comparator.comparing(str -> str.length())).get();

找出字符串穿度最大的值:

String result = list.stream().max(Comparator.comparing(str -> str.length())).get();

为了能够比较字符串长度,在max/min的方法中需要传入一个Comparator对象,在Java8中提供了新的comparing()方法。

reduce

reduce操作可能从一组值中生成一个新的值,比如上面用过的countmaxmin方法其实本质都是reduce操作。

假设一个list集合[1, 2, 3, 4]将其中的每个值分别累加,最后拿到所有值累加的和:

Integer result = list.stream().reduce(0, (x, y) -> x + y);

在上面代码中,x代表当前的和,也就是一个累加器,而y则代码集合中的每一个元素,0则代表和的初始值。当然这种操作方式并不推荐,因为在Java的标准类库中有求和的方法。

多次调用流操作

假设新建一个集合,然后给每个元素+1操作,最后筛选出最大的那个元素。

错误写法:

List<Integer> result1 = Stream.of(1, 2, 3, 4).collect(Collectors.toList());

List<Integer> result2 = result1.stream().map(i -> i + 1).collect(Collectors.toList());

Integer maxInt = result2.stream().max(Comparator.comparing(Integer::intValue)).get();

正确写法:

Integer maxInt = Stream.of(1, 2, 3, 4)
    .map(i -> i + 1)
    .max(Comparator.comparing(Integer::intValue))
    .get();
# Stream  Lambda  Java8 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×