Stream流

前言

这篇详细介绍了Stream流的概念,创建方式,基本操作及部分源码分析。可能有点长哈,大家看起来比较费劲,我自己写的也比较累,光码字就码了很长时间,大家看得过程中可以停下来休息下,喝个茶,斗个地主接着再来看,就安利到这里了,我也该洗洗睡了(码字确实挺累的,写文章也是很累),最后在安利下哈,个人绝得很详细的,祝大家学习愉快哈,看完就可以找到心仪小姐姐(小哥哥)。

1.流的概述及相关概念
java8提供了Stream API,以流的方式来处理数据。那么到底什么是jdk8中所谓的流呢,简单地讲,就是对数组以及集合等数据进行加工处理,比如对数据过滤,映射,遍历等操作。Stream流不是一种数据结构,不存储数据,通过管道的方式获取数据,然后对数据进行加工处理,并不会修改底层的数据源的。那么为什么要使用流呢?因为使用流和lambada表达式(流常与lambada表达式和函数式接口一起使用)对数据进行操作的时候更加简洁,简单,同时流能利用现代多核CPU的特性,提高并行并发效率。流由以下三部分构成:

  • 源(集合,数组等数据源)
  • 零个或多个中间操作,如filter,map等,中间操作可以看做是流水线操作,每一个中间操作都可看做是一条流水线。中间操作执行完后会产生一个新流,原来的数据源没有变化。
  • 终止操作,如forEach,count等,终止操作不会返回Stream类型,可能不返回值,也可能返回其他类型的单个值

流操作的分类

  • 惰性求值,对应于流的中间操作,执行后不直接产生结果
  • 及早求值,对应于流的终止操作,执行后产生结果

举个栗子:

public class StreamTest02 {

   public static void main(String[] args) {
       List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
       //将list中的每个数乘以2,然后在求和
       final Stream<Integer> integerStream = list.stream().map(i -> {
           System.out.println("test----------------------------------");
           return i * 2;
       });
       System.out.println(integerStream);
   }
}

在这里插入图片描述
以上集合list就是Stream流的源,map操作就是中间操作,对应惰性求值,如果后面没有终止操作,map操作只是返回一个新的流,但是并不执行map中的操作,更看不到结果。想要map中的操作执行,需要在后面加上终止操作。

public class StreamTest02 {
   public static void main(String[] args) {
       List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
       //将list中的每个数乘以2,然后在求和
        list.stream().map(i -> {
           System.out.println("test-----------------------------");
           return i * 2;
       }).reduce(Integer::sum);
   }
}

在这里插入图片描述
以上的reduce操作就是终止操作,对应及早求值,执行reduce终止操作时,才一下子把所有的中间操作都执行了。这时我们发现map中的操作执行了,但是reduce归约求和后的结果任然看不见,这是因为我们没有打印输出结果,输出就好了:

public class StreamTest02 {

   public static void main(String[] args) {
       List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
       //将list中的每个数乘以2,然后在求和
        list.stream().map(i -> {
           System.out.println("test-----------------------------");
           return i * 2;
       }).reduce(Integer::sum).ifPresent(System.out::println);
   }
}

在这里插入图片描述
流的分类:

  • 串行流:stream
  • 并行流:parallelStream

这里大致比较一下并行流和串行流的执行效率:

1.使用串行流:

public class StreamTest{

   public static void main(String[] args) {

       List<String> list = new ArrayList<>(10000000);
       for (int i = 0; i < 10000000; i++) {
           list.add(UUID.randomUUID().toString());
       }
       System.out.println("开始排序...");
       long startTime = System.nanoTime();
       list.stream().sorted().count();
       long endTime = System.nanoTime();
       long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
       System.out.println("排序耗时为:" + millis);
   }
}

在这里插入图片描述
2.使用并行流:

public class StreamTes{

   public static void main(String[] args) {

       List<String> list = new ArrayList<>(10000000);
       for (int i = 0; i < 10000000; i++) {
           list.add(UUID.randomUUID().toString());
       }
       System.out.println("开始排序...");
       long startTime = System.nanoTime();
       list.parallelStream().sorted().count();
       long endTime = System.nanoTime();
       long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
       System.out.println("排序耗时为:" + millis);
   }
}

在这里插入图片描述
同样是对一千万数进行排序并统计元素个数,使用并行流要比使用串行流快了不少。是因为并行流充分利用现代CPU的多核特性,使用多个线程并行地执行任务,极大地提高了CPU的利用率,也提高了我们程序的执行效率。

除了以上使用Collection接口中的stream和parallelStream方法来获得并行流外,还可以对流调用parallel()方法,parallel()是BaseStream接口中的一个方法,源码如下:

S parallel();

BaseStream接口中还提供了sequential()方法,表示一个串行流,使流能在串行流和并行流之间切换。
提一下,Stream和BaseStream的关系是父子关系,Stream继承了BaseStream接口。

栗子:

@Test
public void test(){
    students.stream().parallel().filter(sudent ->sudent.getAge() > 16).limit(2).forEach(System.out::println);
}

如果不显示声明为parallel,默认是串行流。

注意:

  • 流自己不存储数据元素
  • 流不改变数据源,相反,会返回一个持有结果的新的Stream
  • 流的操作时延迟执行的,需要结果的时候才执行操作。

2.流的创建方式

2.1通过Stream接口的静态方法of创建流,of方法源码如下:

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

栗子:

Stream<String> stream = Stream.of("beijing", "shanghai", "tianjin");

2.2通过数组的方式创建流

String[] arrays = new String[]{"beijing", "shanghai", "tianjin"};
Stream stringStream = Arrays.stream(arrays);

2.3通过集合方式创建流:

String[] arrays = new String[]{"beijing", "shanghai", "tianjin"};
List<String> list = Arrays.asList(arrays);
Stream stream = list.stream();

2.4通过Sream的迭代方法iterate创建无限流

iterate方法源码分析:

public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {...}

通过源码可知,iterate方法有两个参数,第一个种子参数seed,表示从哪开始,第一个要迭代的元素是哪个。地二个参数是个函数式接口类型的操作参数,UnaryOperator源码如下:

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {  
   static <T> UnaryOperator<T> identity() {
       return t -> t;
   }
}

UnaryOperator继承于Function,给定一个参数,返回结果,不过UnaryOperator的参数和返回值类型一样都是T类型。

举个栗子:

Stream<Integer> integerStream = Stream.iterate(0,i -> i + 2);
//这里执行一下终止操作forEach,遍历下结果,验证这是个无限流
integerStream.forEach(System.out::println);

在这里插入图片描述
以上创建的无限流如果不手动停止运行,就会一直无限地执行下去,直到内存爆满异常退出。
如果要获取有限个数的结果,可以加一个限制个数的中间操作limit操作:

Stream<Integer> integerStream = Stream.iterate(0,i -> i + 2);
integerStream.limit(10).forEach(System.out::println);

在这里插入图片描述
2.5通过Stream的generate静态方法生成一个流

public static<T> Stream<T> generate(Supplier<T> s) {...}

由源码可知,参数是一个Supplier类型的函数式接口,Supplier不传入任何参数,但返回结果。

栗子:

Stream.generate(Math::random).limit(10).forEach(System.out::println);

以上列出了创建Stream的五种方式,但Stream的创建不限于以上五种方法。

3.流的基本操作及源码解析

Student类:

public class Student{
   private String name;
   private Integer age;
   private double score;
   private String level;

   public String getLevel() {
       return level;
   }

   public void setLevel(String level) {
       this.level = level;
   }

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public Integer getAge() {
       return age;
   }

   public void setAge(int age) {
       this.age = age;
   }

   public double getScore() {
       return score;
   }

   public void setScore(double score) {
       this.score = score;
   }

   public Student(String name, Integer age, double score, String level) {
       this.name = name;
       this.age = age;
       this.score = score;
       this.level = level;
   }

   public Student() {
   }

   @Override
   public String toString() {
       return "Student{" +
               "name='" + name + '\'' +
               ", age=" + age +
               ", score=" + score +
               ", level='" + level + '\'' +
               '}';
   }

   @Override
   public boolean equals(Object o) {
       if (this == o) return true;
       if (o == null || getClass() != o.getClass()) return false;
       Student student = (Student) o;
       return age == student.age &&
               Double.compare(student.score, score) == 0 &&
               Objects.equals(name, student.name);
   }

   @Override
   public int hashCode() {
       return Objects.hash(name, age, score);
   }
}

3.1中间操作:

3.1.1筛选与切片

filter–过滤,接收lambada,从流中过滤掉不满足给定条件的元素。源码如下:

Stream<T> filter(Predicate<? super T> predicate);

filter的参数是一个Predicate的断言类型,给定一个参数,返回一个boolean值,判断是否满足要求。

栗子:

@Test
public void test01(){
   students.stream().filter(sudent ->sudent.getAge() > 16).forEach(System.out::println);
}

limit–截断流,是其元素不超过给定数量。

@Test
public void test03(){
   //limit符合短路规则,找到两条就不在向下遍历
   students.stream().filter(sudent ->sudent.getAge() > 16).limit(2).forEach(System.out::println);
}

skip(n)–跳过元素,返回一个跳过了前n个元素的流,若流中的元素个数不足n个,则返回一个空流。

@Test
public void test04(){
   students.stream().filter(sudent ->sudent.getAge() > 16).skip(2).forEach(System.out::println);
}

distinct–筛选,通过流所生成元素的hashCode()和equals()去除重复元素。

@Test
public void test05(){
   //distinct去重,使用distinct去重必须重写hashCode和equals方法
   students.stream().filter(sudent ->sudent.getScore() < 60).distinct().forEach(System.out::println);
}

3.1.2映射

map–接收lambada,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素,最后返回新流。源码如下:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

map方法的参数是一个Function函数式接口类型来表示我们要做的操作,给定一个参数,返回结果,Function的apply方法的参数和返回值类型都是泛型类型,map的返回值是一个带泛型的Stream类型,即返回一个新的流。

栗子:

@Test
public void test06(){
   students.stream().map(Student::getName).forEach(System.out::println);
}

flatMap–扁平映射,接收一个函数作为参数,将流中的每个元素都转换成另一个流,然后把所有流连接成一个流。源码如下:

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

flatMap的参数也是一个Function类型,不过和map不同的是,flatMap的Function接口的第二个泛型类型不是R,而是Stream<? extends R>或它的子类。
在介绍flatMap之前,我们先举一个使用map处理的不太友好的栗子:

private static Stream<Character> filterCharacter(String str){
   List<Character> list = new ArrayList<>();
   for (char c : str.toCharArray()) {
       list.add(c);
   }
   return list.stream();
}

@Test
public void test07(){
   List<String> list = Arrays.asList("hello","world");
   Stream<Stream<Character>> streamStream = list.stream().map(StreamTest04::filterCharacter);
   streamStream.forEach((stream) -> stream.forEach(System.out::println));
}

以上,我们的filterCharacter方法返回的是一个流,而map也返回一个新流,所以接收的类型就是 Stream<Stream>,流中包含流,最外层的这个流包含两个流元素,它们分别是流1[h,e,l,l,o]和流2[w,o,r,l,d],这对我们的遍历不太方便,需要两次遍历才能遍历到具体的元素。

流这块儿不太好调试,所以我把代码稍微修改一下,验证上面的结论:

@Test
public void test07(){
   List<String> list = Arrays.asList("hello","world");
   Stream<Stream<Character>> streamStream = list.stream().map(StreamTest04::filterCharacter);
   streamStream.forEach(stream ->{
       System.out.println(stream);
       stream.forEach(System.out::println);
       System.out.println("---------------------------------------------");
   } );
}

在这里插入图片描述
使用flapMap

@Test
public void test08(){
   List<String> list = Arrays.asList("hello","world");
   final Stream<Character> characterStream = list.stream().flatMap(StreamTest04::filterCharacter);
   characterStream.forEach(System.out::println);
}

flapMap将调用filterCharacter方法后生成的两个映射流给它打碎了,把每个映射流中以前的元素整合到一个新流中,这个新流为[h,e,l,l,o,w,o,r,l,d],并将新流返回。

以上的两个映射方法返回的都是带泛型的Stream类型,而泛型只能用包装类,而不能用基本的数值类型,比如int,long,double等,而只能用他们对应的包装类型,这样自动地装箱拆箱也会损耗一定的性能,所以Stream接口还提供了几个可以使用基本数值类型int,long,double的映射方法,他们分别是mapToInt,mapToLong,mapToDouble。源码分别如下:

IntStream mapToInt(ToIntFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);

以上三个方法的返回类型分别为IntStream,LongStream,DoubleStream。这三个接口就是为了方便我们使用基本数值类型而设计的Stream类型,里面也有一些使用的方法可以直接使用,这里不做介绍,感兴趣的朋友可以看看源码。

举个栗子:

Stream<Integer> stream = Stream.iterate(0, integer -> integer + 2).limit(10);
OptionalInt min = stream.filter(integer -> integer > 2).mapToInt(value -> value * 2).skip(2)
      .limit(2).min();
min.ifPresent(System.out::println);

3.1.3排序

sorted()--自然排序

@Test
public void test09(){
   List<String> list = Arrays.asList("a","b","c","d","e");
   list.stream().sorted().forEach(System.out::println);
}

sorted(Comparator comparator)–定制排序

@Test
public void test10(){
   students.stream().sorted((student1,student2) -> {
       if(student1.getAge().equals(student2.getAge())){
           return student1.getName().compareTo(student2.getName());
       }else {
           return student1.getAge().compareTo(student2.getAge());
       }
   }).forEach(System.out::println);
}

3.2终止操作

3.2.1查找与匹配

allMatch–检查是否匹配所有元素
anyMatch–检查是否至少一个元素
noneMatch–检查是否没有匹配所有元素
findFirst–返回第一个元素
findAny–返回当前流中任意元素
count–返回流中元素的总个数
max–返回流中最大值
min–返回流中最小值

@Test
public void test11(){
   //判断是否所有的学生都小于30岁
   final boolean b = students.stream().allMatch(student -> student.getAge() < 30);
   System.out.println(b);
   //判断是否有学生小于15岁
   System.out.println(students.stream().anyMatch(student -> student.getAge() < 15));
   //判断是否是否没有学生的年龄小于10
   System.out.println(students.stream().noneMatch(student -> student.getAge() < 10));
   //获取所有学生的成绩由大到小排序后的第一个学生信息
   Optional<Student> first = students.stream().sorted((s1, s2)
           -> Double.compare(s2.getScore(), s1.getScore())).findFirst();
   first.ifPresent(System.out::println);
   //通过并行流获取成绩小于60的任意一个学生的信息
   Optional<Student> any = students.parallelStream().filter(student -> student.getScore() < 60).findAny();
   any.ifPresent(System.out::println);
   //获取所有学生的人数
   System.out.println(students.stream().count());
   //获取成绩最高的学生的信息
   Optional<Student> max = students.stream().max(Comparator.comparingDouble(Student::getScore));
   max.ifPresent(System.out::println);
   //获取成绩最低的学生的成绩
   Optional<Double> min = students.stream().map(Student::getScore).min(Double::compareTo);
   min.ifPresent(System.out::println);
}

3.2.2归约

归约(reduce)可以将流中元素通过一个计算函数计算后,得到一个值
Stream源码中有两个reduce方法,一个是不可能为空,返回泛型类型T;一个是结果可能为空,返回Optional,避免空指针。源码如下:

1.结果不可能为空的情况:

T reduce(T identity, BinaryOperator<T> accumulator);

identity指从哪开始计算,accumulator累加器是一个BinaryOperator的函数式接口,源码如下:

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {}

BinaryOperator继承于BiFunction,只是它的两个参数类型和返回值类型都是T类型。

栗子:

@Test
public void test13(){
   List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
   //结果不可能为空,所以不用封装到Optional中
   Integer sum = list.stream().reduce(0, Integer::sum);
   System.out.println(sum);
}

Integer::sum通过静态方法引用调用Integer类中的sum方法求和,sum方法传入两个int值,返回求和结果,还是int值。源码如下:

public static int sum(int a, int b) {
   return a + b;
}

2.结果可能为空的情况:

Optional<T> reduce(BinaryOperator<T> accumulator);

栗子:

@Test
public void test12(){
   //结果可能为空,所以封装到Optional中
   Optional<Double> optional = students.stream().map(Student::getScore).reduce(Double::sum);
   optional.ifPresent(System.out::println);
}

3.2.3收集

collect–将流转换为其他形式,接收一个Collector接口的实现,用于给Stream中元素做汇总的方法,collect源码如下:

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

Collector接口中提供了一些具体操作的函数式接口方法及其他方法:

public interface Collector<T, A, R> {
   Supplier<A> supplier();
   BiConsumer<A, T> accumulator();
   BinaryOperator<A> combiner();
   Function<A, R> finisher();
   Set<Characteristics> characteristics();
}

不过我们一般不直接对Collector接口进行操作,我们常用的是Collectors工具类,Collectors中定义了一个内部类,这个内部类实现了Collector接口:

static class CollectorImpl<T, A, R> implements Collector<T, A, R> {
   private final Supplier<A> supplier;
   private final BiConsumer<A, T> accumulator;
   private final BinaryOperator<A> combiner;
   private final Function<A, R> finisher;
   private final Set<Characteristics> characteristics;
    ...
}

这个工具类中提供了一些静态方法供我们使用,比如我们常用的toList方法,toSet方法,将流转换为集合。

栗子:

@Test
public void test14(){
   //将所有学生的姓名提取出来收集到一个集合中
   List<String> list = students.stream().map(Student::getName).collect(Collectors.toList());
   list.forEach(System.out::println);
}

如果Collectors中没有我们想要将流转换成的类型,还可以使用Collectors提供的toCollection方法:

@Test
public void test15(){
   //将所有学生的姓名提取出来收集到一个集合中
   Collection<String> collect = students.stream().map(Student::getName).collect(Collectors.toCollection(HashSet::new));  
   collect.forEach(System.out::println);
}

collect方法还有一个重载方法,只是这个重载方法用的不是很多,这里也分析一下哈,感兴趣的朋友可以看下哈,不敢兴趣,跳过即可。

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

supplier,提供者函数,是Supplier函数式接口类型,不传入任何参数,有一个返回值,这里是指新结果的容器,也就是collect方法要返回的结果。

accumulator累加器函数,是BiConsumer消费式函数式接口类型,传入两个参数,不返回任何值。这里用于对流中的每一个元素进行遍历,将遍历到的每个元素合并到结果中。

combiner,组合器函数,也是BiConsumer类型,与上面所不同的是,这个BiConsumer的两个参数都是R类型,必须与累加器函数兼容,也就是要与累加器函数中第一个参数代表的的容器类型相同。

举个栗子:

Stream<String> stream = Stream.of("beijing", "shanghai", "tianjin");
List<String> list = stream.collect(() -> new ArrayList<>(),(theList,item) -> theList.add(item),
       (theList1,theList2) -> theList1.addAll(theList2));
list.forEach(System.out::println);

上述代码等价于下面使用方法引用代码:

Stream<String> stream = Stream.of("beijing", "shanghai", "tianjin");
List<String> list = stream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
list.forEach(System.out::println);
上面栗子我们是要把一个stream流转化为一个集合容器,并将stream流流中原来的三个元素,biejing,shanghai,tianjin,转变为新的集合容器的元素。

转换过程(collect三个参数具体运作流程):

首先我们先需要用supplier提供者函数创建一个容器,来装流中的三个元素,最后返回的结果也是这个容器。

接下来我们就要用accumulator这个累加器函数来来遍历stream流中的每个元素,并把每次遍历到的元素都添加到一个集合中。这个集合相当于一个中间集合,并不是我们上面用supplier提供者函数创建的要作为结果返回的集合容器。

最后我们用combiner组合器函数把上面中间集合里的元素一次性地都添加到我们要返回结果的集合容器中,也就是将theList中的元素都添加到theList1中,theList1就相当于supplier提供者函数创建的要作为结果返回的集合容器,theList2就相当于上面的theList.

下面我们来验证一下以上第二步的分析是否和执行结果相同:
public class StreamTest03 {

   public static void main(String[] args) {
       Stream<String> stream = Stream.of("beijing", "shanghei", "tianjin");
       List<String> list = stream.collect(() -> new ArrayList<>(),(theList,item) -> {
                   theList.add(item);
                   System.out.println(theList);
               },
               (theList1,theList2) -> theList1.addAll(theList2));
   }
}

在这里插入图片描述
我们的流中有三个元素,所以在第二步中经过了三次遍历,每次遍历到一个元素,并将每次遍历到的元素添加到theList集合中,三次遍历后theList集合的元素为:[beijing, shanghei, tianjin]。

怎么样?是不是不太好理解,这个重载方法确实比较难容易理解,不过多琢磨琢磨,结合着源码和示例代码练习一下还是会理解的,我学习这个方法也琢磨了挺长时间。

再举一例吧,jdk官方文档的一个栗子:

String concat = stream.collect(StringBuilder::new, StringBuilder::append,
       StringBuilder::append)
       .toString();

我们稍微修改一下,把方法引用改成用lambada表达式更容易理解。

Stream<String> stream = Stream.of("beijing", "shanghai", "tianjin");
StringBuilder collect = stream.collect(() -> new StringBuilder(), (stringBuilder, item) -> stringBuilder.append(item),
       (stringBuilder1, stringBuild2) -> stringBuilder1.append(stringBuild2));
System.out.println(collect.toString());

执行结果是一样的:

在这里插入图片描述
再看一下Collectors.toList()将流转换为list集合的底层实现,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);
}

看到这里我们应该恍然大悟了吧,原来Collectors.toList()方法用的就是我们上面分析的带三个参数的collect的重载方法实现的,二者的本质是一样的。

除了以上的toList,toConllection,toSet方法以外,Collectors工具类还为我们提供了一些其他的实用的方法,比如求总数,平均数,最大值,最小值,分组,分区等,下面就介绍一些我们比较常用的方法。

@Test
public void test16(){
   //获取学生人数
   Long count = students.stream().collect(Collectors.counting());
   System.out.println(count);

   //求学生成绩的平均分
   Double averageScore = students.stream().collect(Collectors.averagingDouble(Student::getScore));
   System.out.println(averageScore);

   //求所有学生总成绩
   double sumScore = students.stream().collect(Collectors.summingDouble(Student::getScore));
   System.out.println(sumScore);

   //获取成绩最高的学生信息
   Optional<Student> max = students.stream().collect(Collectors.maxBy((s1, s2)
           -> Double.compare(s1.getScore(), s2.getScore())));
   max.ifPresent(System.out::println);

   //获取成绩最底的学生的成绩
   Optional<Double> min = students.stream().map(Student::getScore).collect(Collectors.minBy(Double::compareTo));
   min.ifPresent(System.out::println);

   //按照level将所有学生分组
   Map<String, List<Student>> listMap = students.stream().collect(Collectors.groupingBy(Student::getLevel));
   listMap.forEach((level, students) -> {
       System.out.println(level+":"+students);
   });
   System.out.println();

   Map<String, Long> collect = students.stream().collect(Collectors.groupingBy(Student::getLevel, Collectors.counting()));
   collect.forEach((level,counts) -> System.out.println(level + ":" + counts));

   System.out.println();
   //分区,成绩大于等于60为及格区,小于60为不及格区
   Map<Boolean, List<Student>> booleanListMap = students.stream().collect(Collectors.partitioningBy(student
           -> student.getScore() >= 60));
   booleanListMap.forEach((bool, students) -> System.out.println(bool + ":" + students));
}

这样使用Stream流是不是有点像写sql语句,其实jdk8中的stream流挺类似于sql语句的,都是一种描述性语言,让开发者只需要写出想要的结果对应的语句,而不用关心这些语句的底层是怎么实现和执行的。

同时Collectors还有一类统计方法,用于统计个数,总数,最大值,最小值以及求平均数等,这类方法有summarizingInt,summarizingLong,summarizingDouble,分别对应于数值类型的整型,长整型,double类型。以summarizingDouble为例介绍下如何使用。

@Test
public void test17(){
  //统计
   DoubleSummaryStatistics summaryStatistics = students.stream()
           .collect(Collectors.summarizingDouble(Student::getScore));
   System.out.println(summaryStatistics.getCount());
   System.out.println(summaryStatistics.getSum());
   System.out.println(summaryStatistics.getMax());
   System.out.println(summaryStatistics.getMin());
   System.out.println(summaryStatistics.getAverage());
}

随后一个方法,连接方法:

@Test
public void test18(){
   //连接
   String collect = students.stream().map(Student::getName).collect(Collectors.joining(","));
   System.out.println(collect);
}

4.注意

提一点使用流的注意事项,就是我们在使用流时要注意流是不能被重复使用或消费的。不然会报下面的异常。

stream has already been operated upon or closed

栗子:

public class StreamTest06 {

   public static void main(String[] args) {
       Stream<Integer> stream = Stream.iterate(0, integer -> integer + 2).limit(10);
       System.out.println(stream);
       System.out.println(stream.filter(integer -> integer > 2));
       //stream has already been operated upon or closed,因为和上面使用的是同一个流对象,而一个流的对象不能被重复使用
       System.out.println(stream.distinct());//和上面用的同一个stream
   }
}

在这里插入图片描述
修改一下使之用不同的stream:

public class StreamTest06 {

   public static void main(String[] args) {
       Stream<Integer> stream = Stream.iterate(0, integer -> integer + 2).limit(10);
       System.out.println(stream);
       Stream<Integer> integerStream = stream.filter(integer -> integer > 2);
       System.out.println(integerStream);
       System.out.println(integerStream.distinct());
   }
}

5.总结

终于写完了,是不是很啰嗦,太长了,为了保持内容的连续性就没有拆分成几篇文章,脑瓜疼,难免有介绍的不到位的地方,也可能有错别字,虽然我已经检查了好几遍,大家就凑活看吧,能对大家的学习起到点帮助作用就可以了,同时我自己也可以回过头来复习已经忘记的知识。如果大家发现有不对的地方,欢迎留言指正,错别字也可以。输出不易,大家给点个赞也行(皮一下哈),同时也欢迎大家关注微信公众号,后期会有更多的输出,共同学习,不断进步,持续输出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值