Java 二十二篇:Stream流

操作符

什么是操作符呢?操作符就是对数据进行的一种处理工作,一道加工程序;就好像工厂的工人对流水线上的产品进行一道加工程序一样。

Java 二十二篇:Stream流

Stream流
  • stream就是同一个迭代器,单向,不可往复,数据只能遍历一次,遍历一次后即用尽了
生成Stream Source
  • 从Collection和数组


    • Collection.stream()
    • Colleaction.parallSteam()

    • Arrays.stream(T array) or Stream.of()
  • 从BufferedReader


    • java.io.BufferedReader.lines()
  • 静态工厂


    • java.util.stream.IntStream.range()
    • java.nio.file.Files.walk()
  • 自己构建


    • java.util.Spliterator
  • 其他


    • Random.ints()
    • BitSet.stream()

    • Pattern.splitAsStream(java.lang.CharSequence)
    • JarFile.stream()

Java 二十二篇:Stream流

流(Stream)的操作类型分为两种:
  • 中间操作符

对于数据流来说,中间操作符在执行制定处理程序后,数据流依然可以传递给下一级的操作符。

中间操作符包含8种(排除了parallel,sequential,这两个操作并不涉及到对数据流的加工操作):

● map(mapToInt,mapToLong,mapToDouble) 转换操作符,把比如A->B,这里默认提供了转intlongdouble的操作符。
● flatmap(flatmapToInt,flatmapToLong,flatmapToDouble) 拍平操作比如把 int[]{2,3,4} 拍平 变成 234 也就是从原来的一个数据变成了3个数据,这里默认提供了拍平成int,long,double的操作符。
● limit 限流操作,比如数据流中有10个 我只要出前3个就可以使用。
● distint 去重操作,对重复元素去重,底层使用了equals方法。
● filter 过滤操作,把不想要的数据过滤。
● peek 挑出操作,如果想对数据进行某些操作,如:读取、编辑修改等。
● skip 跳过操作,跳过某些元素。
● sorted(unordered) 排序操作,对元素排序,前提是实现Comparable接口,当然也可以自定义比较器。
  • 终止操作符

数据经过中间加工操作,就轮到终止操作符上场了;终止操作符就是用来对数据进行收集或者消费的,数据到了终止操作这里就不会向下流动了,终止操作符只能使用一次。


● collect 收集操作,将所有数据收集起来,这个操作非常重要,官方的提供的Collectors 提供了非常多收集器,可以说Stream 的核心在于Collectors。
● count 统计操作,统计最终的数据个数。
● findFirst、findAny 查找操作,查找第一个、查找任何一个 返回的类型为Optional。
● noneMatch、allMatch、anyMatch 匹配操作,数据流中是否存在符合条件的元素 返回值为bool 值。
● min、max 最值操作,需要自定义比较器,返回数据流中最大最小的值。
● reduce 规约操作,将整个数据流的值规约为一个值,count、min、max底层就是使用reduce。
● forEach、forEachOrdered 遍历操作,这里就是对最终的数据进行消费了。
● toArray 数组操作,将数据流的元素转换成数组。

这里只介绍了Stream,并没有涉及到IntStream、LongStream、DoubleStream,这三个流实现了一些特有的操作符,我将在后续文章中介绍到。

说了这么多,只介绍这些操作符还远远不够;俗话说,实践出真知。那么,Let‘s go。

流的使用详解

对 Stream 的使用就是实现一个 filter-map-reduce 过程,产生一个最终结果,或者导致一个副作用(side effect)

流的构造与转换
// 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

  • IntStream、LongStream、DoubleStream。当然我们也可以用 Stream、Stream >、Stream,但是 boxing 和 unboxing 会很耗时,所以特别为这三种基本数值型提供了对应的 Stream。

数值流的构造:

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

补充:

  • foreach语句是java5的新特征之一,在遍历数组、集合方面,foreach为开发人员提供了极大的方便。


    • foreach语句是for语句的特殊简化版本,但是foreach语句并不能完全取代for语句,然而,任何的foreach语句都可以改写为for语句版本。
    • foreach的语句格式:for(元素类型t 元素变量x : 遍历对象obj){ 引用了x的java语句; }

    • 二、foreach语句的局限性 通过上面的例子可以发现,如果要引用数组或者集合的索引,则foreach语句无法做到,foreach仅仅老老实实地遍历数组或者集合一遍。
  • 流转换为其他数据结构

  • // 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();
    一个 Stream 只可以使用一次,上面的代码只是示例,为了简洁而重复使用了数次(正常开发只能使用一次)。
  • 我感觉的是转化为stack流


  • 流的操作

    • 当把一个数据结构包装成 Stream 后,就要开始对里面的元素进行各类操作了。

    1. 常见的操作: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

  • 集合有两种方式生成流:

stream() 为集合创建串行流

parallelStream() – 为集合创建并行流

Java 二十二篇:Stream流

具体用法

流的常用创建方法

1.1 使用Collection下的 stream() 和 parallelStream() 方法

List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); //获取一个顺序流
Stream<String> parallelStream = list.parallelStream(); //获取一个并行流

1.2 使用Arrays 中的 stream() 方法,将数组转成流

Integer[] nums = new Integer[10];
Stream<Integer> stream = Arrays.stream(nums);
1.3 使用Stream中的静态方法:of()、iterate()、generate()

Stream<Integer> stream = Stream.of(1,2,3,4,5,6);

Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 2).limit(6);
stream2.forEach(System.out::println); // 0 2 4 6 8 10

Stream<Double> stream3 = Stream.generate(Math::random).limit(2);
stream3.forEach(System.out::println);
1.4 使用 BufferedReader.lines() 方法,将每行内容转成流

BufferedReader reader = new BufferedReader(new FileReader("F:\test_stream.txt"));
Stream<String> lineStream = reader.lines();
lineStream.forEach(System.out::println);
1.5 使用 Pattern.splitAsStream() 方法,将字符串分隔成流

Pattern pattern = Pattern.compile(",");
Stream<String> stringStream = pattern.splitAsStream("a,b,c,d");
stringStream.forEach(System.out::println);

2. 流的中间操作

2.1 筛选与切片

filter:过滤流中的某些元素

limit(n):获取n个元素

skip(n):跳过n元素,配合limit(n)可实现分页

distinct:通过流中元素的 hashCode() 和 equals() 去除重复元素

Stream<Integer> stream = Stream.of(6, 4, 6, 7, 3, 9, 8, 10, 12, 14, 14);

Stream<Integer> newStream = stream.filter(s -> s > 5) //6 6 7 9 8 10 12 14 14
.distinct() //6 7 9 8 10 12 14
.skip(2) //9 8 10 12 14
.limit(2); //9 8
newStream.forEach(System.out::println);

2.2 映射
map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。

List<String> list = Arrays.asList("a,b,c", "1,2,3");

//将每个元素转成一个新的且不带逗号的元素
Stream<String> s1 = list.stream().map(s -> s.replaceAll(",", ""));
s1.forEach(System.out::println); // abc 123

Stream<String> s3 = list.stream().flatMap(s -> {
//将每个元素转换成一个stream
String[] split = s.split(",");
Stream<String> s2 = Arrays.stream(split);
return s2;
});
s3.forEach(System.out::println); // a b c 1 2 3

2.3 排序 sorted():自然排序,流中元素需实现Comparable接口 sorted(Comparator com):定制排序,自定义Comparator排序器

List<String> list = Arrays.asList("aa", "ff", "dd");
//String 类自身已实现Compareable接口
list.stream().sorted().forEach(System.out::println);// aa dd ff

Student s1 = new Student("aa", 10);
Student s2 = new Student("bb", 20);
Student s3 = new Student("aa", 30);
Student s4 = new Student("dd", 40);
List<Student> studentList = Arrays.asList(s1, s2, s3, s4);

//自定义排序:先按姓名升序,姓名相同则按年龄升序
studentList.stream().sorted(
(o1, o2) -> {
if (o1.getName().equals(o2.getName())) {
return o1.getAge() - o2.getAge();
} else {
return o1.getName().compareTo(o2.getName());
}
}
).forEach(System.out::println);

2.4 消费 peek:如同于map,能得到流中的每一个元素。但map接收的是一个Function表达式,有返回值;而peek接收的是Consumer表达式,没有返回值。

Student s1 = new Student("aa", 10);
Student s2 = new Student("bb", 20);
List<Student> studentList = Arrays.asList(s1, s2);

studentList.stream()
.peek(o -> o.setAge(100))
.forEach(System.out::println);

//结果:
Student{name='aa', age=100}
Student{name='bb', age=100}

3. 流的终止操作

3.1 匹配、聚合操作

allMatch:接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回true,否则返回false

noneMatch:接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回true,否则返回false

anyMatch:接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回false

findFirst:返回流中第一个元素

findAny:返回流中的任意元素

count:返回流中元素的总个数

max:返回流中元素最大值

min:返回流中元素最小值

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

boolean allMatch = list.stream().allMatch(e -> e > 10); //false
boolean noneMatch = list.stream().noneMatch(e -> e > 10); //true
boolean anyMatch = list.stream().anyMatch(e -> e > 4); //true

Integer findFirst = list.stream().findFirst().get(); //1
Integer findAny = list.stream().findAny().get(); //1

long count = list.stream().count(); //5
Integer max = list.stream().max(Integer::compareTo).get(); //5
Integer min = list.stream().min(Integer::compareTo).get(); //1

3.2 规约操作

```java
Optional<T> reduce(BinaryOperator<T> accumulator):第一次执行时,accumulator函数的第一个参数为流中的第一个元素,第二个参数为流中元素的第二个元素;第二次执行时,第一个参数为第一次函数执行的结果,第二个参数为流中的第三个元素;依次类推。
T reduce(T identity, BinaryOperator<T> accumulator):流程跟上面一样,只是第一次执行时,accumulator函数的第一个参数为identity,而第二个参数为流中的第一个元素。
<U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner):在串行流(stream)中,该方法跟第二个方法一样,即第三个参数combiner不会起作用。在并行流(parallelStream)中,我们知道流被fork join出多个线程进行执行,此时每个线程的执行流程就跟第二个方法reduce(identity,accumulator)一样,而第三个参数combiner函数,则是将每个线程的执行结果当成一个新的流,然后使用第一个方法reduce(accumulator)流程进行规约。
```
//经过测试,当元素个数小于24时,并行时线程数等于元素个数,当大于等于24时,并行时线程数为16
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24);

Integer v = list.stream().reduce((x1, x2) -> x1 + x2).get();
System.out.println(v); // 300

Integer v1 = list.stream().reduce(10, (x1, x2) -> x1 + x2);
System.out.println(v1); //310

Integer v2 = list.stream().reduce(0,
(x1, x2) -> {
System.out.println("stream accumulator: x1:" + x1 + " x2:" + x2);
return x1 - x2;
},
(x1, x2) -> {
System.out.println("stream combiner: x1:" + x1 + " x2:" + x2);
return x1 * x2;
});
System.out.println(v2); // -300

Integer v3 = list.parallelStream().reduce(0,
(x1, x2) -> {
System.out.println("parallelStream accumulator: x1:" + x1 + " x2:" + x2);
return x1 - x2;
},
(x1, x2) -> {
System.out.println("parallelStream combiner: x1:" + x1 + " x2:" + x2);
return x1 * x2;
});
System.out.println(v3); //197474048

3.3 收集操作

 collect:接收一个Collector实例,将流中元素收集成另外一个数据结构。  Collector<T, A, R> 是一个接口,有以下5个抽象方法:  Supplier<A> supplier():创建一个结果容器A  BiConsumer<A, T> accumulator():消费型接口,第一个参数为容器A,第二个参数为流中元素T。  BinaryOperator<A> combiner():函数接口,该参数的作用跟上一个方法(reduce)中的combiner参数一样,将并行流中各个子进程的运行结果(accumulator函数操作后的容器A)进行合并。  Function<A, R> finisher():函数式接口,参数为:容器A,返回类型为:collect方法最终想要的结果R。  Set<Characteristics> characteristics():返回一个不可变的Set集合,用来表明该Collector的特征。有以下三个特征:  CONCURRENT:表示此收集器支持并发。(官方文档还有其他描述,暂时没去探索,故不作过多翻译)  UNORDERED:表示该收集操作不会保留流中元素原有的顺序。  IDENTITY_FINISH:表示finisher参数只是标识而已,可忽略。  注:如果对以上函数接口不太理解的话,可参考我另外一篇文章:Java 8 函数式接口


3.3.1 Collector 工具库:Collectors

Student s1 = new Student("aa", 10,1);
Student s2 = new Student("bb", 20,2);
Student s3 = new Student("cc", 10,3);
List<Student> list = Arrays.asList(s1, s2, s3);

//装成list
List<Integer> ageList = list.stream().map(Student::getAge).collect(Collectors.toList()); // [10, 20, 10]

//转成set
Set<Integer> ageSet = list.stream().map(Student::getAge).collect(Collectors.toSet()); // [20, 10]

//转成map,注:key不能相同,否则报错
Map<String, Integer> studentMap = list.stream().collect(Collectors.toMap(Student::getName, Student::getAge)); // {cc=10, bb=20, aa=10}

//字符串分隔符连接
String joinName = list.stream().map(Student::getName).collect(Collectors.joining(",", "(", ")")); // (aa,bb,cc)

//聚合操作
//1.学生总数
Long count = list.stream().collect(Collectors.counting()); // 3
//2.最大年龄 (最小的minBy同理)
Integer maxAge = list.stream().map(Student::getAge).collect(Collectors.maxBy(Integer::compare)).get(); // 20
//3.所有人的年龄
Integer sumAge = list.stream().collect(Collectors.summingInt(Student::getAge)); // 40
//4.平均年龄
Double averageAge = list.stream().collect(Collectors.averagingDouble(Student::getAge)); // 13.333333333333334
// 带上以上所有方法
DoubleSummaryStatistics statistics = list.stream().collect(Collectors.summarizingDouble(Student::getAge));
System.out.println("count:" + statistics.getCount() + ",max:" + statistics.getMax() + ",sum:" + statistics.getSum() + ",average:" + statistics.getAverage());

//分组
Map<Integer, List<Student>> ageMap = list.stream().collect(Collectors.groupingBy(Student::getAge));
//多重分组,先根据类型分再根据年龄分
Map<Integer, Map<Integer, List<Student>>> typeAgeMap = list.stream().collect(Collectors.groupingBy(Student::getType, Collectors.groupingBy(Student::getAge)));

//分区
//分成两部分,一部分大于10岁,一部分小于等于10岁
Map<Boolean, List<Student>> partMap = list.stream().collect(Collectors.partitioningBy(v -> v.getAge() > 10));

//规约
Integer allAge = list.stream().map(Student::getAge).collect(Collectors.reducing(Integer::sum)).get(); //40

3.3.2 Collectors.toList() 解析

//toList 源码
public static <T> Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
(left, right) -> {
left.addAll(right);
return left;
}, CH_ID);
}

//为了更好地理解,我们转化一下源码中的lambda表达式
public <T> Collector<T, ?, List<T>> toList() {
Supplier<List<T>> supplier = () -> new ArrayList();
BiConsumer<List<T>, T> accumulator = (list, t) -> list.add(t);
BinaryOperator<List<T>> combiner = (list1, list2) -> {
list1.addAll(list2);
return list1;
};
Function<List<T>, List<T>> finisher = (list) -> list;
Set<Collector.Characteristics> characteristics = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));

return new Collector<T, List<T>, List<T>>() {
@Override
public Supplier supplier() {
return supplier;
}

@Override
public BiConsumer accumulator() {
return accumulator;
}

@Override
public BinaryOperator combiner() {
return combiner;
}

@Override
public Function finisher() {
return finisher;
}

@Override
public Set<Characteristics> characteristics() {
return characteristics;
}
};

}

具体用法:

map/flatMap

//转换大写,wordList为单词集合List<String>类型
List<String> output = wordList.stream().map(String::toUpperCase).collect(Collectors.toList());

求平方数

//这段代码生成一个整数 list 的平方数 {1, 4, 9, 16}。
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
List<Integer> squareNums = nums.stream().map(n -> n * n).collect(Collectors.toList());
  • 从上面的例子可以看出来,map 生成的是个 1:1 映射,每个输入元素,都按照规则转换成为另外一个元素(一对一映射)

  • 一对多映射

//将最底层元素抽出来放到一起,最终 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());
List<Integer> list =outputStream.collect(Collectors.toList());
System.out.println(list.toString());

filter

filter 对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。

//留下偶数,经过条件“被 2 整除”的 filter,剩下的数字为 {2, 4, 6}。
Integer[] sixNums = {1, 2, 3, 4, 5, 6};
Integer[] evens =Stream.of(sixNums).filter(n -> n%2 == 0).toArray(Integer[]::new);
//把每行的单词用 flatMap 整理到新的 Stream,然后保留长度不为 0 的,就是整篇文章中的全部单词了。
//REGEXP为正则表达式,具体逻辑具体分析
List<String> output = reader.lines().
flatMap(line -> Stream.of(line.split(REGEXP))).
filter(word -> word.length() > 0).collect(Collectors.toList());

forEach

forEach 方法接收一个 Lambda 表达式,然后在 Stream 的每一个元素上执行该表达式

//打印所有男性姓名,roster为person集合类型为List<Pserson>
// Java 8
roster.stream().filter(p -> p.getGender() == Person.Sex.MALE).forEach(p -> System.out.println(p.getName()));
// Pre-Java 8
for (Person p : roster) {
if (p.getGender() == Person.Sex.MALE) {
System.out.println(p.getName());
}
}
  • 当需要为多核系统优化时,可以 parallelStream().forEach(),只是此时原有元素的次序没法保证,并行的情况下将改变串行时操作的行为,此时 forEach 本身的实现不需要调整,而 Java8 以前的 for 循环 code 可能需要加入额外的多线程逻辑。


    • 另外一点需要注意,forEach 是 terminal 操作,因此它执行后,Stream 的元素就被“消费”掉了,你无法对一个 Stream 进行两次 terminal 运算
  • 注意:一个stream不可以使用两次

  • 具有相似功能的 intermediate 操作 peek 可以达到上述目的。

peek 对每个元素执行操作并返回一个新的 StreamStream.of("one","two","three","four").

filter(e ->e.length()>3).

peek(e ->System.out.println("Filtered value: "+e)).

map(String::toUpperCase) .

peek(e ->System.out.println("Mapped value: "+e)).

collect(Collectors.toList());
  • findFirst 这是一个 termimal 兼 short-circuiting 操作,它总是返回 Stream 的第一个元素,或者空。


    • 这里比较重点的是它的返回值类型:Optional。这也是一个模仿 Scala 语言中的概念,作为一个容器,它可能含有某值,或者不包含。使用它的目的是尽可能避免 NullPointerException。

  • String strA = " abcd ", strB = null;

    print(strA);

    print("");

    print(strB);

    getLength(strA);

    getLength("");

    getLength(strB);

    //输出text不为null的值public static void print(String text)
    {
    Java 8 Optional.ofNullable(text).ifPresent(System.out::println);
    Pre - Java 8 if (text != null) {
    System.out.println(text);
    }
    }

    //输出text的长度,避免空指针
    public static int getLength(String text) {
    Java 8
    return Optional.ofNullable(text).map(String::length).orElse(-1);
    Pre - Java 8
    return if (text != null) ?text.length() :-1;
    }

    • 在更复杂的 if (xx != null) 的情况中,使用 Optional 代码的可读性更好,而且它提供的是编译时检查,能极大的降低 NPE 这种 Runtime Exception 对程序的影响,或者迫使程序员更早的在编码阶段处理空值问题,而不是留到运行时再发现和调试。
    • Stream 中的 findAny、max/min、reduce 等方法等返回 Optional 值。还有例如 IntStream.average() 返回 OptionalDouble 等等
  • reduce

  • 这个方法的主要作用是把 Stream 元素组合起来。


    • 它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。也有没有起始值的情况,这时会把 Stream 的前面两个元素组合起来,返回的是 Optional。
  •      reduce用例
    字符串连接,concat = "ABCD"String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
    求最小值,minValue = -3.0double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
    求和,sumValue = 10, 有起始值int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
    求和,sumValue = 10, 无起始值,返回Optional,所以有get()方法sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
    过滤,字符串连接,concat = "ace"concat = Stream.of("a", "B", "c", "D", "e", "F") .filter(x -> x.compareTo("Z") > 0) .reduce("", String::concat);
  • limit/skip


    • limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素(它是由一个叫 subStream 的方法改名而来)。
  • //limit 和 skip 对运行次数的影响public 
    void testLimitAndSkip() { List<Person> persons = new ArrayList();
    for (int i = 1; i <= 10000; i++) {
    Person person = new Person(i, "name" + i);
    persons.add(person);
    }
    List<String> personList2 = persons.stream()
    .map(Person::getName).limit(10).skip(3)
    .collect(Collectors.toList());
    System.out.println(personList2);
    }
    private class Person {
    public int no;
    private String name;
    public Person (int no, String name) {
    this.no = no;
    this.name = name;
    }
    public String getName() {
    System.out.println(name);
    return name;
    }
    }
    //结果name1name2name3name4name5name6name7name8name9name10[name4, name5, name6, name7, name8, name9, name10]
    // 这是一个有 10,000 个元素的 Stream,但在 short-circuiting 操作 limit 和 skip 的作用下,
    // 管道中 map 操作指定的 getName() 方法的执行次数为 limit 所限定的 10 次,而最终返回结果在跳过前 3 个元素后只有后面 7 个返回。

    • 有一种情况是 limit/skip 无法达到 short-circuiting 目的的,就是把它们放在 Stream 的排序操作后,原因跟 sorted 这个 intermediate 操作有关:此时系统并不知道 Stream 排序后的次序如何,所以 sorted 中的操作看上去就像完全没有被 limit 或者 skip 一样。
  •   List<Person> persons = new ArrayList(); 
    for (int i = 1; i <= 5; i++) {
    Person person = new Person(i, "name" + i);
    persons.add(person);
    }
    List<Person> personList2 = persons.stream().sorted((p1, p2) -> p1.getName().compareTo(p2.getName())).limit(2).collect(Collectors.toList());
    System.out.println(personList2);
    //结果name2name1name3name2name4name3name5name4[stream.StreamDW$Person@816f27d, stream.StreamDW$Person@87aac27]//虽然最后的返回元素数量是
    // 2,但整个管道中的 sorted 表达式执行次数没有像前面例子相应减少。
  • sorted

  • 对 Stream 的排序通过 sorted 进行,它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。

  List<Person> persons = new ArrayList(); 
for (int i = 1; i <= 5; i++) {
Person person = new Person(i, "name" + i);
persons.add(person);
}
List<Person> personList2 = persons.stream().limit(2)
.sorted((p1, p2) -> p1.getName().compareTo(p2.getName())).collect(Collectors.toList());
System.out.println(personList2);
//结果name2name1[stream.StreamDW$Person@6ce253f1, stream.StreamDW$Person@53d8d10a]

min/max/distinct

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

//找出最长一行的长度
BufferedReader br = new BufferedReader(new FileReader("c:\Service.log"));
int longest = br.lines().mapToInt(String::length).max().getAsInt();
br.close();
System.out.println(longest);
//找出全文的单词,转小写,并排序,使用 distinct 来找出不重复的单词。单词间只有空格
List<String> words = br.lines()
.flatMap(line -> Stream.of(line.split(" "))) .filter(word -> word.length() > 0)
.map(String::toLowerCase) .distinct().sorted() .collect(Collectors.toList());
br.close();
System.out.println(words);

Match

Stream 有三个 match 方法,从语义上说:

  • allMatch:Stream 中全部元素符合传入的 predicate,返回 true

  • anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true

  • noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true


    • 它们都不是要遍历全部元素才能返回结果。例如 allMatch 只要一个元素不满足条件,就 skip 剩下的所有元素,返回 false。

  • //Match使用示例
    List<Person> persons = new ArrayList();
    persons.add(new Person(1, "name" + 1, 10));
    persons.add(new Person(2, "name" + 2, 21));
    persons.add(new Person(3, "name" + 3, 34));
    persons.add(new Person(4, "name" + 4, 6));
    persons.add(new Person(5, "name" + 5, 55));
    boolean isAllAdult = persons.stream().allMatch(p -> p.getAge() > 18);
    System.out.println("All are adult? " + isAllAdult);
    boolean isThereAnyChild = persons.stream().anyMatch(p -> p.getAge() < 12);
    System.out.println("Any child? " + isThereAnyChild);
    //结果All are adult? falseAny child? true
  • 自己生成流
  • 通过实现 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 wayIntStream.generate(() -> (int) (System.nanoTime() % 100)).limit(10).forEach(System.out::println);
  • Stream.generate() 还接受自己实现的 Supplier。例如在构造海量测试数据的时候,用某种自动的规则给每一个变量赋值;或者依据公式计算 Stream 的每个元素值。这些都是维持状态信息的情形。

  •   Stream.generate(new PersonSupplier()).
    limit(10).forEach(p -> System.out.println(p.getName() + ", " + p.getAge()));
    private 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));
    }
    }
    //结果StormTestUser1, 9StormTestUser2, 12StormTestUser3, 88StormTestUser4,
    // 51StormTestUser5, 22StormTestUser6, 28StormTestUser7, 81StormTestUser8,
    // 51StormTestUser9, 4StormTestUser10, 76
  • 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 大小。
  • 用 Collectors 来进行 reduction 操作

    • java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的 reduction 操作,例如转变输出为 Collection,把 Stream 元素进行归组。
  • groupingBy/partitioningBy
  • //按照年龄归组
    Map<Integer, List<Person>> personGroups = Stream.generate(new PersonSupplier())
    .limit(100).collect(Collectors.groupingBy(Person::getAge));
    Iterator it = personGroups.entrySet().iterator();while (it.hasNext()) {
    Map.Entry<Integer, List<Person>> persons = (Map.Entry) it.next();
    System.out.println("Age " + persons.getKey() + " = " + persons.getValue().size());
    }
    //上面的 code,首先生成 100 人的信息,然后按照年龄归组,相同年龄的人放到同一个 list 中,
    // 如下的输出:Age 0 = 2Age 1 = 2Age 5 = 2Age 8 = 1Age 9 = 1Age 11 = 2……
  • Map<Boolean, List<Person>> children = Stream.generate(new PersonSupplier())  
    .limit(100).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: 23 Adult number: 77
  • Stream 的特性可以归纳为:

    • 不是数据结构它没有内部存储,它只是用操作管道从 source(数据结构、数组、generator function、IO channel)抓取数据。它也绝不修改自己所封装的底层数据结构的数据。例如 Stream 的 filter 操作会产生一个不包含被过滤元素的新 Stream,而不是从 source 删除那些元素。所有 Stream 的操作必须以 lambda 表达式为参数不支持索引访问你可以请求第一个元素,但无法请求第二个,第三个,或最后一个。不过请参阅下一项。很容易生成数组或者 List惰性化很多 Stream 操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始。Intermediate 操作永远是惰性化的。并行能力当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的。可以是无限的集合有固定大小,Stream 则不必。limit(n) 和 findFirst() 这类的 short-circuiting 操作可以对无限的 Stream 进行运算并很快完成。

    • 当把一个数据结构包装成 Stream 后,就要开始对里面的元素进行各类操作了,
  • Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。

Java 二十二篇:Stream流



Java 二十二篇:Stream流



本篇文章来源于微信公众号: 小刘Java之路

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/11174.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!