Java 8新特性(4) Stream

java.util.Stream 表示了某一种元素的序列,在这些元素上可以进行各种操作。Stream 操作可以是中间操作,也可以是完结操作。完结操作会返回一个某种类型的值,而中间操作会返回流对象本身,并且你可以通过多次调用同一个流操作方法来将操作结果串起来(就像 StringBuffer 的 append 方法一样)。Stream 是在一个源的基础上创建出来的,例如 java.util.Collection 中的 list 或者 set(map 不能作为 Stream 的源)。Stream 操作往往可以通过顺序或者并行两种方式来执行。

流的操作类型分为两种:

  • Intermediate:一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
  • Terminal:一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。

还有一种操作被称为 short-circuiting。用以指:

  • 对于一个 intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的 Stream,但返回一个有限的新 Stream。
  • 对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。

当操作一个无限大的 Stream,而又希望在有限时间内完成操作,则在管道内拥有一个 short-circuiting 操作是必要非充分条件。

流的构造与转换

构造流的几种常见方法

// 1. Individual values
Stream stream = Stream.of("a", "b", "c");

// 2. Arrays
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);

// 3. Collections
List<String> list = Arrays.asList(strArray);
stream = list.stream();

对于基本数值型,目前有三种对应的包装类型 Stream:IntStreamLongStreamDoubleStream。当然我们也可以用 Stream、Stream、Stream,但是 boxing 和 unboxing 会很耗时,所以特别为这三种基本数值型提供了对应的 Stream。

IntStream.of(new int[]{1, 2, 3}).forEach(System.out::println); // 1,2,3
IntStream.range(1, 3).forEach(System.out::println);  // 1,2
IntStream.rangeClosed(1, 3).forEach(System.out::println); // 1,2,3

流转换为其它数据结构,一个 Stream 只可以使用一次,下面的代码为了简洁而重复使用了数次。

Stream<String> stream = Stream.of("a", "b", "c");
// 1. Array
String[] strArray1 = stream.toArray(String[]::new);

// 2. Collection
List<String> list1 = stream.collect(Collectors.toList());
List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
Set set1 = stream.collect(Collectors.toSet());
Stack stack1 = stream.collect(Collectors.toCollection(Stack::new));

// 3. String
String str = stream.collect(Collectors.joining()).toString();

流的操作

  • Intermediate:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

  • Terminal:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

  • Short-circuiting:anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

首先,我们通过 string 类型的 list 的形式创建示例数据:

List<String> stringCollection = new ArrayList<>();
stringCollection.add("ddd2");
stringCollection.add("aaa2");
stringCollection.add("bbb1");
stringCollection.add("aaa1");
stringCollection.add("bbb3");
stringCollection.add("ccc");
stringCollection.add("bbb2");
stringCollection.add("ddd1");

filter

filter 接受一个 predicate 接口类型的变量,并将所有流对象中的元素进行过滤。该操作是一个中间操作,因此它允许我们在返回结果的基础上再进行其他的流操作(forEach),例子见下文。

forEach

forEach 接受一个 function 接口类型的变量,用来执行对每一个元素的操作。forEach 是一个中止操作。它不返回流,所以我们不能再调用其他的流操作。

stringCollection
    .stream()
    .filter((s) -> s.startsWith("a"))
    .forEach(System.out::println);

// "aaa2", "aaa1"

peek

peek 方法也是接收一个 Consumer 功能型接口,它与 forEach 的区别就是它会返回 Stream 接口,也就是说 forEach 是一个 Terminal 操作,而 peek 是一个 Intermediate 操作,forEach 完了以后 Stream 就消费完了,不能继续再使用,而 peek 还可以继续使用。

stringCollection
    .stream()
    .filter(s -> s.startsWith("a"))
    .peek(System.out::println)
    .map(s -> s.toUpperCase())
    .peek(System.out::println)
    .collect(Collectors.toList());

// aaa2
// AAA2
// aaa1
// AAA1

peek 是一个 Intermediate 操作,它并不会马上执行,当 collect 的时候才会把 peek 和 collect 一起执行,来提高效率,所以等于一个元素执行完所有操作之后再对下一个元素进行处理。

如果没有对流进行任何操作 peek 就不会执行。

sorted

sorted 是一个中间操作,能够返回一个排过序的流对象的视图。流对象中的元素会默认按照自然顺序进行排序,除非你自己指定一个 Comparator 接口来改变排序规则。

stringCollection
    .stream()
    .sorted()
    .filter((s) -> s.startsWith("a"))
    .forEach(System.out::println);

// "aaa1", "aaa2"

一定要记住,sorted 只是创建一个流对象排序的视图,而不会改变原来集合中元素的顺序。原来 string 集合中的元素顺序是没有改变的。

System.out.println(stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1

map/flatMap

map 是一个对于流对象的中间操作,通过给定的方法,它能够把流对象中的每一个元素对应到另外一个对象上。下面的例子就演示了如何把每个 string 都转换成大写的 string。 不但如此,你还可以把每一种对象映射成为其他类型。对于带泛型结果的流对象,具体的类型还要由传递给 map 的泛型方法来决定。

stringCollection
    .stream()
    .map(String::toUpperCase)
    .sorted((a, b) -> b.compareTo(a))
    .forEach(System.out::println);

// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"

map 生成的是个 1:1 映射,每个输入元素,都按照规则转换成为另外一个元素。还有一些场景,是一对多映射关系的,这时需要 flatMap。flatMap 把 input Stream 中的层级结构扁平化,就是将最底层元素抽出来放到一起,最终 output 的新 Stream 里面已经没有 List 了,都是直接的数字。

Stream<List<Integer>> inputStream = Stream.of(
        Arrays.asList(1),
        Arrays.asList(2, 3),
        Arrays.asList(4, 5, 6)
);
Stream<Integer> outputStream = inputStream.
        flatMap((childList) -> childList.stream());
outputStream.forEach(s -> System.out.print(s + ", "));
// 1, 2, 3, 4, 5, 6,
List<String> strs = Arrays.asList("好,好,学", "习,天,天", "向,上");

List<String[]> strArray = strs.stream().map(str -> str.split(",")).collect(Collectors.toList());

// flatMap 与 map 的区别在于 flatMap是将一个流中的每个值都转成一个个流,然后再将这些流扁平化成为一个流 。
List<String> strList = strs.stream().map(str -> str.split(","))
        .flatMap(Arrays::stream)
        .collect(Collectors.toList());

System.out.println("strList => " + strList);
// strList => [好, 好, 学, 习, 天, 天, 向, 上]

anyMatch/allMatch/noneMatch

匹配操作有多种不同的类型,都是用来判断某一种规则是否与流对象相互吻合的。所有的匹配操作都是终结操作,只返回一个 boolean 类型的结果。

boolean anyStartsWithA =
    stringCollection
        .stream()
        .anyMatch((s) -> s.startsWith("a"));  // 任意

System.out.println(anyStartsWithA);      // true

boolean allStartsWithA =
    stringCollection
        .stream()
        .allMatch((s) -> s.startsWith("a"));  // 全部

System.out.println(allStartsWithA);      // false

boolean noneStartsWithZ =
    stringCollection
        .stream()
        .noneMatch((s) -> s.startsWith("z"));  // 全都不

System.out.println(noneStartsWithZ);      // true

count

count 是一个终结操作,它的作用是返回一个数值,用来标识当前流对象中包含的元素数量。

long startsWithB =
    stringCollection
        .stream()
        .filter((s) -> s.startsWith("b"))
        .count();

System.out.println(startsWithB);    // 3

reduce

reduce 操作是一个终结操作,它能够把 Stream 元素组合起来。字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。该操作的结果会放在一个 Optional 变量里返回。

例如 Stream 的 sum 就相当于:

Integer[] sixNums = {1, 2, 3, 4, 5, 6};
Stream<Integer> integers = Stream.of(sixNums);
// 1,初始值,和一个 BinaryOperator 累加器
Integer sum = integers.reduce(0, (a, b) -> a+b);
// 或
Integer sum = integers.reduce(0, Integer::sum);
// 2,无初始值,BinaryOperator 积累函数
Integer sum = integers.reduce(Integer::sum).orElse(0);
// 3,初始值,BiFunction 累加器和 BinaryOperator 类型的组合器函数
Integer sum = integers.reduce(0, (sum, p) -> sum += p, (sum1, sum2) -> sum1 + sum2);

添加一些调试输出来扩展3

Integer[] sixNums = {1, 2, 3};
Stream<Integer> integers = Stream.of(sixNums);
Integer sums = integers.reduce(0,
    (sum, p) -> {System.out.println("accumulator: sum="+sum+", p="+p);return sum += p;},
    (sum1, sum2) -> {System.out.println("combiner: sum1="+sum1+", sum2="+sum2);return sum1 + sum2;});

// accumulator: sum=0, p=1
// accumulator: sum=1, p=2
// accumulator: sum=3, p=3

可以看到,累加器函数做了所有工作。组合器从来没有调用过。

下面以并行方式执行相同的流

Integer sums = integers.parallel().reduce(0,
    (sum, p) -> {System.out.println("accumulator: sum="+sum+", p="+p);return sum += p;},
    (sum1, sum2) -> {System.out.println("combiner: sum1="+sum1+", sum2="+sum2);return sum1 + sum2;});

// accumulator: sum=0, p=1
// accumulator: sum=0, p=3
// accumulator: sum=0, p=2
// combiner: sum1=2, sum2=3
// combiner: sum1=1, sum2=5

这个流的并行执行行为会完全不同。现在实际上调用了组合器。由于累加器被并行调用,组合器需要用于计算部分累加值的总和。

limit/skip

limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素。

stringCollection.stream().limit(3).forEach(System.out::println);
// ddd2, aaa2, bbb1
stringCollection.stream().skip(2).limit(3).forEach(System.out::println);
// bbb1, aaa1, bbb3
stringCollection.stream().limit(3).skip(2).forEach(System.out::println);
// bbb1

min/max/distinct

min 和 max 的功能也可以通过对 Stream 元素先排序,再 findFirst 来实现,但前者的性能会更好,为 O(n),而 sorted 的成本是 O(n log n)。同时它们作为特殊的 reduce 方法被独立出来也是因为求最大最小值是很常见的操作。

Integer[] sixNums = {1, 2, 3, 4, 5, 6, 6};
Stream<Integer> integers = Stream.of(sixNums);
integers.min(Integer::compareTo);  // 返回值 Optional,terminal 操作,下面为简洁而重复使用
integers.max(Integer::compareTo);  // 返回值 Optional,terminal 操作,下面为简洁而重复使用
integers.distinct().forEach(System.out::println);

findFirst/findAny

findFirst:返回第一个元素的 Optional,findAny:返回任意元素的 Optional。

integers.findFirst().ifPresent(System.out::println);  // 终结操作
integers.findAny().ifPresent(System.out::println);

mapToInt/mapToLong/mapToDouble/mapToObj

有时需要将通常的对象数据流转换为基本数据流,或者相反。出于这种目的,对象数据流支持特殊的映射操作mapToInt()、mapToLong() 和 mapToDouble():

Stream.of("a1", "a2", "a3")
    .map(s -> s.substring(1))
    .mapToInt(Integer::parseInt)
    .max()
    .ifPresent(System.out::println);  // 3

基本数据流可以通过mapToObj()转换为对象数据流:

IntStream.range(1, 4)
    .mapToObj(i -> "a" + i)
    .forEach(System.out::println);

// a1
// a2
// a3

下面是组合示例:浮点数据流首先映射为整数数据流,之后映射为字符串的对象数据流:

Stream.of(1.0, 2.0, 3.0)
    .mapToInt(Double::intValue)
    .mapToObj(i -> "a" + i)
    .forEach(System.out::println);

// a1
// a2
// a3

Stream.generate

通过实现 Supplier 接口,你可以自己来控制流的生成。这种情形通常用于随机数、常量的 Stream,或者需要前后元素间维持着某种状态信息的 Stream。把 Supplier 实例传递给 Stream.generate() 生成的 Stream,默认是串行(相对 parallel 而言)但无序的(相对 ordered 而言)。由于它是无限的,在管道中,必须利用 limit 之类的操作限制 Stream 大小。

生成 10 个随机整数

Random seed = new Random();
Supplier<Integer> random = seed::nextInt;
Stream.generate(random).limit(10).forEach(System.out::println);
//Another way
IntStream.generate(() -> (int) (System.nanoTime() % 1000)).
        limit(10).forEach(System.out::println);
// -1718100697 -848168940 -2008666631 1248576610 -1224934545 943407587 -689001410 1506937317 -1229890500 976825946
// 50 37 88 5 17 29 10 49 31 25

Stream.generate() 还接受自己实现的 Supplier。例如在构造海量测试数据的时候,用某种自动的规则给每一个变量赋值;或者依据公式计算 Stream 的每个元素值。这些都是维持状态信息的情形。

Stream.generate(new PersonSupplier()).
        limit(10).
        forEach(p -> System.out.println(p.getName() + ", " + p.getAge()));

class PersonSupplier implements Supplier<Person> {
    private int index = 0;
    private Random random = new Random();
    @Override
    public Person get() {
        return new Person(index++, "StormTestUser" + index, random.nextInt(100));
    }
}
/* output:
tormTestUser1, 0
StormTestUser2, 1
StormTestUser3, 2
StormTestUser4, 3
StormTestUser5, 4
StormTestUser6, 5
StormTestUser7, 6
StormTestUser8, 7
StormTestUser9, 8
StormTestUser10, 9
*/

Stream.iterate

iterate 跟 reduce 操作很像,接受一个种子值,和一个 UnaryOperator(例如 f)。然后种子值成为 Stream 的第一个元素,f(seed) 为第二个,f(f(seed)) 第三个,以此类推。

生成一个等差数列

Stream.iterate(0, n -> n + 3).limit(10). forEach(x -> System.out.print(x + " "));
// 0 3 6 9 12 15 18 21 24 27

与 Stream.generate 相仿,在 iterate 时候管道必须有 limit 这样的操作来限制 Stream 大小。

collect

collect 方法跟 reduce 方法功能很类似,都是聚合方法。不同的是,reduce 方法在操作每一个元素时总创建一个新值,而 collect 方法只是修改现存的值,而不是创建一个新值。

方法定义一

<R> R collect(Supplier<R> supplier,
                  BiConsumer<R, ? super T> accumulator,
                  BiConsumer<R, R> combiner);

<R, A> R collect(Collector<? super T, A, R> collector);

调用方式如下,很明显第一个参数 supplier 为结果存放容器,第二个参数 accumulator 为结果如何添加到容器的操作,第三个参数 combiner 则为多个容器的聚合策略.

import com.google.common.collect.Lists;

// 例一,字符串相加:
String concat = stringStream.collect(StringBuilder::new, StringBuilder::append,StringBuilder::append).toString();
//等价于上面,这样看起来应该更加清晰
String concat = stringStream.collect(() -> new StringBuilder(),(l, x) -> l.append(x), (r1, r2) -> r1.append(r2)).toString();

// 例二,对 List 值求和:
// 由于基本类型都是不可变类型,所以这里用数组当做容器
final Integer[] integers = Lists.newArrayList(1, 2, 3, 4, 5)
        .stream()
        .collect(() -> new Integer[]{0}, (a, x) -> a[0] += x, (a1, a2) -> a1[0] += a2[0]);

// 例三:
Lists.<Person>newArrayList().stream()
        .collect(HashMap<Integer, List<Person>>::new,
                (h, x) -> {
                    List<Person> value = h.getOrDefault(x.getAge(), Lists.newArrayList());
                    value.add(x);
                    h.put(x.getAge(), value);
                },
                HashMap::putAll
        );

方法定义二

<R, A> R collect(Collector<? super T, A, R> collector);

接收一个 Collector 接口作为参数,它由四个不同的操作组成:供应器(supplier)、累加器(accumulator)、组合器(combiner)和终止器(finisher)。如果要自己实现它会很麻烦,好在 java.util.stream 包中给我们提供了一个叫 Collectors 的类。通过 Collectors 这个类可以很容易得到一个 Collector 对象,这个类中提供了很多统计的操作和创建集合的操作。

构建自己的特殊收集器。我们希望将流中的所有人转换为一个字符串,包含所有大写的名称,并以 | 分割。为了完成它,我们通过 Collector.of() 创建了一个新的收集器。我们需要传递一个收集器的四个组成部分:供应器、累加器、组合器和终止器。

Collector<Person, StringJoiner, String> personNameCollector =
    Collector.of(
        () -> new StringJoiner(" | "),          // supplier
        (j, p) -> j.add(p.name.toUpperCase()),  // accumulator
        (j1, j2) -> j1.merge(j2),               // combiner
        StringJoiner::toString);                // finisher

String names = persons
    .stream()
    .collect(personNameCollector);

System.out.println(names);  // MAX | PETER | PAMELA | DAVID

由于 Java 中的字符串是不可变的,我们需要一个助手类 StringJointer。让收集器构造我们的字符串。供应器最开始使用相应的分隔符构造了这样一个 StringJointer。累加器用于将每个人的大写名称加到 StringJointer 中。组合器知道如何把两个 StringJointer 合并为一个。最后一步,终结器从 StringJointer 构造出预期的字符串。

Collectors

toList/toSet/toCollection

List<Person> persons = Arrays.asList(
        new Person("Max", 18), new Person("Peter", 23),
        new Person("Pamela", 23), new Person("David", 12));

// 从流的元素中构造了一个列表
List<Person> filtered = persons
        .stream()
        .filter(p -> p.name.startsWith("P"))
        .collect(Collectors.toList());  // 如果需要以Set来替代List,只需要使用Collectors.toSet()就好了。

System.out.println(filtered);  // [Peter, Pamela]

toList 使用了 ArrayList 作为列表的实现。toSet 方法使用了 HashSet 作为集合的实现来存储结果集。时候我们可能会想要人为指定容器的实际类型,这个需求可通过 Collectors.toCollection(Supplier<C> collectionFactory) 方法完成。

// 使用 toCollection() 指定规约容器的类型
ArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));
HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));

toMap/toConcurrentMap

可以使用 toMap 收集器将一个流转换为一个映射。toMap 收集器需要两个映射方法来获得映射的键和值。在下面展示的代码中,Person::getAge 是接收一个 Person 并产生一个只包含该 Person 的 Age 键的 Functiont->t 是一个用来返回 Person 本身的 lambda 表达式,也可以使用 Function.identity()

Map<Integer, Person> map = persons.stream().collect(Collectors.toMap(Person::getAge, t->t));

从一个流中创建映射的代码会在存在重复的键时抛出异常。将会得到一个类似下面的错误。

Exception in thread "main" java.lang.IllegalStateException: Duplicate key Person{name='Peter'}
    at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133)
    ...

可以通过使用 toMap 方法的另一个变体来处理重复问题,它允许我们指定一个合并方法。这个合并方法允许用户他们指定想如何处理多个值关联到同一个键的冲突。在下面展示的代码中,我们只是使用了新的值,当然也可以编写一个智能的算法来处理冲突。

Map<Integer, Person> map = persons.stream().collect(Collectors.toMap(Person::getAge, t->t, (t1, t2) -> t2));

你可以通过使用 toMap 方法的第三个变体指定其他的映射实现。这需要你指定将用来存储结果的 Map 和 Supplier。

Map<Integer, Person> map = persons.stream().collect(Collectors.toMap(Person::getAge, t->t, (t1, t2) -> t2, LinkedHashMap::new));

类似于 toMap 收集器,也有 toConcurrentMap 收集器,它产生一个 ConcurrentMap 而不是 HashMap。

maxBy/minBy

查找最大最小值

persons.stream().collect(Collectors.maxBy(Comparator.comparingInt(Person::getAge)))
        .ifPresent(System.out::println);  // Person{name='Peter'}
persons.stream().collect(Collectors.minBy(Comparator.comparingInt(Person::getAge)))
        .ifPresent(System.out::println);  // Person{name='David'}

// 类似于
persons.stream().max(Comparator.comparingInt(Person::getAge))
        .ifPresent(System.out::println);
persons.stream().min(Comparator.comparingInt(Person::getAge))
        .ifPresent(System.out::println);

collectingAndThen

转换函数返回的类型

// 返回最大 Age 的 Person
Person maxAgePerson = persons.stream().collect(Collectors.collectingAndThen(
        Collectors.maxBy(Comparator.comparingInt(Person::getAge)),
        Optional::get));  // 为转换函数,转换最终的数据

counting

计算流中元素的个数

long count = persons.stream().collect(Collectors.counting());  // 4
long count = persons.stream().count();
int count = persons.size();

summingInt/summingLong/summingDouble

求和

long sumAge = persons.stream().collect(Collectors.summingInt(Person::getAge));

// 类似于
long sumAge = persons.stream().mapToInt(Person::getAge).sum();

averagingInt/averagingDouble/averagingLong

求平均值

double averag = persons.stream().collect(Collectors.averagingInt(Person::getAge)); // 19.0

summarizingInt/summarizingDouble/summarizingLong

综合方法,和,平均,最大最小全求出来

IntSummaryStatistics statistics = persons.stream().collect(Collectors.summarizingInt(Person::getAge));
System.out.println("the max:" + statistics.getMax());
System.out.println("the min:" + statistics.getMin());
System.out.println("the average:" + statistics.getAverage());
System.out.println("the sum:" + statistics.getSum());
System.out.println("the count:" + statistics.getCount());

joining

连接流中元素上指定的属性

// 已 ; 分隔
persons.stream().map(Person::getName).collect(Collectors.joining(";"));
// Max;Peter;Pamela;David

// 已 ; 分隔,前缀 [,后缀 ]
persons.stream().map(Person::getName).collect(Collectors.joining(";", "[", "]"));
// [Max;Peter;Pamela;David]

// 字符串相连
persons.stream().map(Person::getName).collect(Collectors.joining());
// MaxPeterPamelaDavid

reducing

从一个累加器的初始值开始,使用BinaryOperator与流中的元素逐个集合,最后将流规约为单个值。

persons.stream().collect(Collectors.reducing(0, Person::getAge, Integer::sum));
persons.stream().map(Person::getAge).reduce(0, Integer::sum);

persons.stream().collect(Collectors.reducing((a, b) -> a)).ifPresent(System.out::println);
persons.stream().reduce((a, b) -> a).ifPresent(System.out::println);

groupingBy

按照年龄归组

Map<Integer, List<Person>> personGroups = persons.stream().
        collect(Collectors.groupingBy(Person::getAge));
Iterator it = personGroups.entrySet().iterator();
while (it.hasNext()) {
    Map.Entry<Integer, List<Person>> ps = (Map.Entry) it.next();
    System.out.println("Age " + ps.getKey() + " = " + ps.getValue().size());
}
// Age 18 = 1
// Age 23 = 2
// Age 12 = 1

partitioningBy

按照未成年人和成年人归组

Map<Boolean, List<Person>> children = persons.stream().
        collect(Collectors.partitioningBy(p -> p.getAge() < 18));
System.out.println("Children number: " + children.get(true).size());
System.out.println("Adult number: " + children.get(false).size());
// Children number: 1
// Adult number: 3

在使用条件“年龄小于 18”进行分组后可以看到,不到 18 岁的未成年人是一组,成年人是另外一组。partitioningBy 其实是一种特殊的 groupingBy,它依照条件测试的是否两种结果来构造返回的数据结构,get(true)get(false) 能即为全部的元素对象。

IntStream、LongStream 和 DoubleStream

Java8 还自带了特殊种类的流,用于处理基本数据类型 int、long 和 double。

IntStream 可以使用 IntStream.range() 替换通常的 for 循环:

IntStream.range(1, 4)
    .forEach(System.out::println);

// 1
// 2
// 3

所有这些基本数据流都像通常的对象数据流一样,但有一些不同。基本的数据流使用特殊的 lambda 表达式,例如,IntFunction 而不是 Function,IntPredicate 而不是 Predicate。而且基本数据流支持额外的聚合终止操作 sum() 和 average()

Arrays.stream(new int[] {1, 2, 3})
    .map(n -> 2 * n + 1)
    .average()  // OptionalDouble
    .ifPresent(System.out::println);  // 5.0

复用数据流

Java8 的数据流不能被复用。一旦你调用了任何终止操作,数据流就关闭了。否则会产生 java.lang.IllegalStateException: stream has already been operated upon or closed 异常。

要克服这个限制,我们需要为每个我们想要执行的终止操作创建新的数据流调用链。例如,我们创建一个数据流供应器,来构建新的数据流,并且设置好所有衔接操作:

Supplier<Stream<String>> streamSupplier =
    () -> Stream.of("d2", "a2", "b1", "b3", "c")
            .filter(s -> s.startsWith("a"));

streamSupplier.get().anyMatch(s -> true);   // ok
streamSupplier.get().noneMatch(s -> true);  // ok

每次对 get() 的调用都构造了一个新的数据流,我们将其保存来调用终止操作。

参考

https://wizardforcel.gitbooks.io/modern-java/content/ch1.html
https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/index.html
https://juejin.im/post/59c7d39b6fb9a00a3d136291
https://blog.csdn.net/u013291394/article/details/52662761


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com

文章标题:Java 8新特性(4) Stream

文章字数:5.5k

本文作者:Bin

发布时间:2019-03-13, 21:14:22

最后更新:2019-08-06, 00:07:35

原始链接:http://coolview.github.io/2019/03/13/Java8/Java%208%E6%96%B0%E7%89%B9%E6%80%A7(4)%20Stream/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录