JAVA8给我带了什么——流的概念和收集器

到如今为止,笔者不敢给流下定义,从概念来说他应该也是一种数据元素才是。但是在咱们前面的代码例子中咱们能够看到他更多的好像在表示他是一组处理数据的行为组合。这让笔者很难去理解他的定义。因此笔者不表态。各位同志自行理解吧。
在没有流之前,处理集合里面的数据通常都会用到显示的迭代器。用一下前面学生的例子吧。目标是得到学分大于5的前俩位同窗。java

 1 package com.aomi;
 2 
 3 import java.util.ArrayList;
 4 import java.util.Iterator;
 5 import java.util.List;
 6 import static java.util.stream.Collectors.toList;
 7 
 8 public class Main {
 9 
10     public static void main(String[] args) {
11         // TODO Auto-generated method stub
12 
13         List<Student> stus = getSources();
14 
15         Iterator<Student> ite = stus.iterator();
16 
17         List<String> names = new ArrayList<>();
18         int limit = 2;
19         while (ite.hasNext() && limit > 0) {
20 
21             Student stu = ite.next();
22 
23             if (stu.getScore() > 5) {
24 
25                 names.add(stu.getName());
26                 limit--;
27             }
28         }
29 
30         for (String name : names) {
31             System.out.println(name);
32         }
33 
34     }
35 
36     public static List<Student> getSources() {
37         List<Student> students = new ArrayList<>();
38 
39         Student stu1 = new Student();
40 
41         stu1.setName("lucy");
42         stu1.setSex(0);
43         stu1.setPhone("13700227892");
44         stu1.setScore(9);
45 
46         Student stu2 = new Student();
47         stu2.setName("lin");
48         stu2.setSex(1);
49         stu2.setPhone("15700227122");
50         stu2.setScore(9);
51 
52         Student stu3 = new Student();
53         stu3.setName("lili");
54         stu3.setSex(0);
55         stu3.setPhone("18500227892");
56         stu3.setScore(8);
57 
58         Student stu4 = new Student();
59 
60         stu4.setName("dark");
61         stu4.setSex(1);
62         stu4.setPhone("16700555892");
63         stu4.setScore(6);
64 
65         students.add(stu1);
66         students.add(stu2);
67         students.add(stu3);
68         students.add(stu4);
69 
70         return students;
71     }
72 
73 }

若是用流的话是这样子的。数组

public static void main(String[] args) {
        // TODO Auto-generated method stub

        List<Student> stus = getSources();

        List<String> names = stus.stream()
                .filter(st -> st.getScore() > 5)
                .limit(2)
                .map(st -> st.getName())
                .collect(toList());
        for (String name : names) {
            System.out.println(name);
        }

    }

 把这俩段代码相比较主要是为了说明一个概念:之前作法都是在外部迭代,最为体现就是笔者在外面定义了一个集合names 。而流却什么也没有,如今咱们应该能清楚感觉到流是在内部迭代。也就是说流已经帮你作好了迭代。咱们只要传入相关的函数就能够获得想要的结果。至于内部迭代的好处,笔者没有办法亲身的感觉,惟一的感受就是代码变的简单明了了。可是官方说Stream库为了咱们在内部迭代里面作了不少优化和充公利用性能的操做。好比并行操做。因此笔者就听官方了。安全

事实上,在用流的过程当中,咱们用到不少方法函数。好比上面的limit方法,filter方法等。这个定义为流操做。可是不论是什么操做,你必需要有一个数据源吧。总结以下:框架

  • 数据源:用于生成流的数据,好比集合。
  • 流操做:相似于limit方法,filter方法。

流还有一种特色——部分流操做是没有执行的。通常都是在collect函数执行的时候,才开始执行个个函数。因此咱们能够细分一下流操做:ide

  • 数据源:用于生成流的数据,好比集合。
  • 中间操做:相似于limit方法,filter方法。这些操做作变了一个操做链,有一点流水线的概念。
  • 终端操做:执行上面的操做链。好比collect函数。

从上面的讲解咱们就能够感受流好像是先收集相关的目标操做,什么意思呢?就是先把要作的事情计划一下,最后一声令下执行。而下这个命令是collect函数。这一点跟.NET的Linq是很像的。同时记得他只能执行一次。也就是说这个流执行一次以后,就不可能在用了。
笔者列一下之前的用到的函数函数

forEach:终端
collect:终端
count:终端
limit:中间
filter:中间
map:中间
sorted:中间

到目前为止咱们用到的流都是经过集合来建一个流。笔者对此历来没有讲过。如今笔者来说些构建流的方式。
在stream库里面为咱们提供了这样子一个方法——Stream.of性能

package com.aomi;

import java.util.Optional;
import java.util.stream.Stream;

public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		Stream stream = Stream.of("I", "am", "aomi");

		Optional<String> firstWord = stream.findFirst();
		
		if(firstWord.isPresent())
		{
			System.out.println("第一个字:"+firstWord.get());
		}
	}

}

运行结果:优化

去看一下of方法的代码。以下spa

public static<T> Stream<T> of(T... values) {
        return Arrays.stream(values);
    }

说明咱们可能指定一个类型来建一个流。上面能够修改成code

Stream<String> stream = Stream.of("I", "am", "aomi");

findFirst函数用于表示返回第一个值。那就是可能数据源是一个空呢?因此他有能够会返回null。因此就是用一个叫Optional类的表示能够为空。这样子咱们就能够用Optional类的方法进一步作安全性的操做。好比判断有没有值(isPresent())
笔者想要建一个int类型的数组流玩玩。为了方便笔者便试给一下上面的代码。却发现报错了。

若是我把int改成Integer呢?没有问题了。因此注意要用引用类型的。int类型对应为Integer类型。

 1 package com.aomi;
 2 
 3 import java.util.Optional;
 4 import java.util.stream.Stream;
 5 
 6 public class Main {
 7 
 8     public static void main(String[] args) {
 9         // TODO Auto-generated method stub
10 
11         Stream<Integer> stream = Stream.of(1, 2, 9);
12 
13         Optional<Integer> firstWord = stream.findFirst();
14         
15         if(firstWord.isPresent())
16         {
17             System.out.println("第一个字:"+firstWord.get());
18         }
19     
20     }
21 
22 }

运行结果:

那想要用int类型呢?什么办呢?改改

 1 package com.aomi;
 2 
 3 import java.util.OptionalInt;
 4 import java.util.stream.IntStream;
 5 
 6 public class Main {
 7 
 8     public static void main(String[] args) {
 9         // TODO Auto-generated method stub
10 
11         IntStream stream = IntStream.of(1, 2, 9);
12 
13         OptionalInt firstWord = stream.findFirst();
14         
15         if(firstWord.isPresent())
16         {
17             System.out.println("第一个字:"+firstWord.getAsInt());
18         }
19     
20     }
21 
22 }

运行结果:

咱们以上面的例子来一个猜想:是否是Double类型,只要修改成DoubleStream就行呢?试试。

 1 package com.aomi;
 2 
 3 import java.util.OptionalDouble;
 4 import java.util.stream.DoubleStream;
 5 
 6 public class Main {
 7 
 8     public static void main(String[] args) {
 9         // TODO Auto-generated method stub
10 
11         DoubleStream stream = DoubleStream.of(1.3, 2.3, 9.5);
12 
13         OptionalDouble firstWord = stream.findFirst();
14         
15         if(firstWord.isPresent())
16         {
17             System.out.println("第一个字:"+firstWord.getAsDouble());
18         }
19     
20     }
21 
22 }

运行结果:

结果很明显,咱们的猜想是对的。因此见意若是你操做的流是一个int或是double的话,请进可能的用XxxStream 来建流。这样子在流的过程当中不用进行拆装和封装了。必竟这是要性能的。在看一下若是数据源是一个数组的状况咱们如何生成流呢?

1  public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,
2                                                              CharSequence prefix,
3                                                              CharSequence suffix) {
4         return new CollectorImpl<>(
5                 () -> new StringJoiner(delimiter, prefix, suffix),
6                 StringJoiner::add, StringJoiner::merge,
7                 StringJoiner::toString, CH_NOID);
8     }

在看一个叫toList函数的代码。

1 public static <T>
2     Collector<T, ?, List<T>> toList() {
3         return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
4                                    (left, right) -> { left.addAll(right); return left; },
5                                    CH_ID);
6     }

咱们发现他会共同的返回一个Collector类型。从上面咱们就能够知道他的任务就是用去处理最后数据。咱们把他定为收集器。让咱们看一下收集器的接口代码吧;

 1 public interface Collector<T, A, R> {
 2 
 3     Supplier<A> supplier();
 4 
 5     BiConsumer<A, T> accumulator();
 6 
 7     BinaryOperator<A> combiner();
 8 
 9     Function<A, R> finisher();
10 
11     Set<Characteristics> characteristics();
12 }

光看前面四个方法是否是有一点熟悉的感受。想要说明这个五个方法的做用。就必须明白一个概念——并行归约。前面笔者讲过流是一个内部迭代,也说Stream库为咱们作一个不少优化的事情。其中一个就是并行。他用到了JAVA 7引入的功能——分支/合并框架。也就是说流会以递归的方式拆分红不少子流,而后子流能够并行执行。最后在俩俩的子流的结果合并成一个最终结果。而这俩俩合并的行为就叫归约 。如图下。引用于《JAVA8实战》

 

 

咱们必须根据图上的意思来走。子流的图里面会调用到Collector类的三个方法。

  • supplier方法:用建立数据存储的地方。
  • accumulator方法:用于子流执行过程的迭代工做。便是遍历每一项都会执行。因此能够这里作一些工做。
  • finisher方法:返回最后的结果,你能够在这里进一步处理结果。

每个子流结束这以后,就是俩俩合并。这个时候就要看流的机制图了。

  • combiner方法:会传入每个子流的结果过来,咱们就能够在这里在作一些工做。
  • finisher方法:返回最后的结果。同上面子流的同样子。

好像没有characteristics什么事情。不是这样子的。这个方法是用来讲明当前这个流具有哪些优化。这样子执行流的时候,就能够很清楚的知道要以什么样子的方式执行了。好比并行。
他是一个enum类。值以下

  • UNORDERED:这个表示执行过程当中结果不受归约和遍历的影响
  • CONCURRENT:表示能够多个线和调用accumulator方法。而且能够执行并行。固然前无序数据的才并行。除非收集器标了UNORDERED。
  • IDENTITY_FINISH:表示这是一个恒等函数,就是作告终果也同样子。不用作了能够跳过了。

由了上面的讲说明,咱们在来写一个本身的收集器吧——去除相同的单词
DistinctWordCollector类:

 1 package com.aomi;
 2 
 3 import java.util.ArrayList;
 4 import java.util.Collections;
 5 import java.util.EnumSet;
 6 import java.util.List;
 7 import java.util.Set;
 8 import java.util.function.BiConsumer;
 9 import java.util.function.BinaryOperator;
10 import java.util.function.Function;
11 import java.util.function.Supplier;
12 import java.util.stream.Collector;
13 
14 public class DistinctWordCollector implements Collector<String, List<String>, List<String>> {
15 
16     @Override
17     public Supplier<List<String>> supplier() {
18         // TODO Auto-generated method stub
19         return () -> new ArrayList<String>();
20     }
21 
22     /**
23      * 子流的处理项的过程
24      */
25     @Override
26     public BiConsumer<List<String>, String> accumulator() {
27         // TODO Auto-generated method stub
28         return (List<String> src, String val) -> {
29 
30             if (!src.contains(val)) {
31                 src.add(val);
32             }
33         };
34     }
35 
36     /**
37      * 俩俩并合的执行函数
38      */
39     @Override
40     public BinaryOperator<List<String>> combiner() {
41         // TODO Auto-generated method stub
42         return (List<String> src1, List<String> src2) -> {
43             for (String val : src2) {
44                 if (!src1.contains(val)) {
45                     src1.add(val);
46                 }
47             }
48             return src1;
49         };
50     }
51 
52     @Override
53     public Function<List<String>, List<String>> finisher() {
54         // TODO Auto-generated method stub
55         return Function.identity();
56     }
57 
58     @Override
59     public Set<Characteristics> characteristics() {
60         // TODO Auto-generated method stub
61         return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH, Characteristics.CONCURRENT));
62     }
63 
64 }

Main:

 1 public static void main(String[] args) {
 2         // TODO Auto-generated method stub
 3 
 4         List<String> words = Arrays.asList("aomi","lili","lucy","aomi","Nono");
 5 
 6         List<String> vals = words.stream().collect(new DistinctWordCollector());
 7         
 8         for (String val : vals) {
 9             System.out.println(val);
10         }
11     }
12

运行结果

结果肯定就是咱们想要的——去掉了重复的aomi

相关文章
相关标签/搜索