Java8 Stream流 - 高效快速的处理集合

概述:

  • Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据
  • Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象
  • Stream流就是将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
  • 元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果

值得注意的是,Stream流有一些特性:

  • Stream流不是一种数据结构,不保存数据,它只是在原数据集上定义了一组操作
  • 这些操作是惰性的,即每当访问到流中的一个元素,才会在此元素上执行这一系列操作
  • Stream不保存数据,故每个Stream流只能使用一次

一、Stream流基本说明

在Stream流上的操作,可以分成两种:Intermediate(中间操作)和Terminal(终止操作)。中间操作的返回结果都是Stream,故可以多个中间操作叠加;终止操作用于返回我们最终需要的数据,只能有一个终止操作。

1.如何产生Stream流
  • Collection集合类的stream()方法或者parallelStream()方法产生

    // 集合接口实现类直接调用方法创建流
    List list = new ArrayList();
    list.stream();
    list.parallelStream();
    
    // collection接口创建流
    Collection<String> collection = Arrays.asList("a", "b", "c");
    Stream<String> streamOfCollection = collection.stream();
    
  • Stream的静态方法:

    // Stream.of() 产生了一个包含整型泛型的Stream流
    Stream<Integer> integerStream = Stream.of(1, 2, 3, 4);
    
    // 如果创建空流,要使用empty()方法,避免为没有元素的流返回Null.
    Stream<Object> empty = Stream.empty();
    
  • Arrays.stream()从数组或其一部分创建

    String[] arr = new String[]{"a", "b", "c"};
    Stream<String> streamOfArrayFull = Arrays.stream(arr);
    // arr数组,1-beginIndex,2-endIndex
    Stream<String> streamOfArrayPart = Arrays.stream(arr, 1, 3);
    
  • Stream.builder()构建流

    Stream<String> streamBuilder = 
        Stream.<String>builder().add("a").add("b").add("c").build();
    
  • Stream.generate()或者iterate()流

    // generate() 方法需要就收一个Supplier对象作为元素生成.
    // 注意这个方法生成了一个无限流,也就是说没有像集合那样有长度,它是无限的
    // 此外,这个流它的顺序是无序的
    // limit(10) 限定了它的长度为10
    Stream<String> streamGenerated =Stream.generate(() -> "element").limit(10);
    
    // 这个流产生的是同样的无限流,但是是有顺序的
    Stream<Integer> streamIterated = Stream.iterate(40, n -> n + 2).limit(20);
    

其实产生流的方法有很多,这里只列举了常见的创建流的方法,其余的不在多述。

2.Stream流的中间操作API(Intermediate

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

方法 说明
filter 过滤操作,把不想要的数据过滤掉
map 转换操作,有mapToInt(),mapToDouble(),mapToLong()方法,有点类似于类型转换
flatMap 平铺操作,也就是将一些数据展开,比如把int[]{2,3,4}展开变为2,3,4
limit 限流操作,比如数据流中有10个 我只要出前3个就可以使用
distinct 去重操作,对重复元素去重,底层使用了equals方法
peek 挑出操作,如果想对数据进行某些操作,如:读取、编辑修改等
skip 跳过操作,跳过某些元素不做处理
sorted 排序操作,对元素排序,前提是实现Comparable接口
3.Stream流的终止操作API(Termediate

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

方法 说明
collect 收集操作,将所有数据收集起来,这个操作非常重要,官方的提供的Collectors 提供了非常多收集器,可以说Stream 的核心在于Collectors
count 统计操作,统计最终的数据个数
findFirst 查找第一个匹配的元素
findAny 查找任何一个匹配的元素
noneMatch -
allMatch 三个Match用来匹配数据流中是否符合条件的元素,返回布尔值
anyMatch -
max,min,sum 求最大值,最小值,求和操作
reduce 规约操作,将整个数据流的值规约为一个值,count、min、max底层就是使用reduce
forEach forEach、forEachOrdered 遍历操作
toArray 数组操作,将数据流的元素转换成数组

上面列举了一些Stream流中间,终止操作API,实际当中方法更多。下面会结合代码来进行举例说明

二、使用例子

  • 准备一些数据

    Student stu1 = new Student("1","xiaoming","man","20");
    Student stu2 = new Student("2","wangwu","woman","22");
    Student stu3 = new Student("3","lisi","man","24");
    Student stu4 = new Student("4","xiyangyang","man","26");
    Student stu5 = new Student("5","zhangsan","man","27");
    Student stu6 = new Student("6","xiaohong","woman","27");
    
    List<Student> data = new ArrayList<>();
    data.add(stu1);
    data.add(stu2);
    data.add(stu3);
    data.add(stu4);
    data.add(stu5);
    data.add(stu6);
    
  • Filter过滤

    // 过滤所有的女性,注意这里的过滤指的是只获取性别为女性的学生对象
    List<Student> collect = data.stream().filter(student -> 	 "woman".equals(student.getSex())).collect(Collectors.toList());
    System.out.println(collect);
    // 控制台输出
    // [Student{id='2', name='wangwu', sex='woman', age='22'}, Student{id='6', name='xiaohong', sex='woman', age='27'}]
    
    // 获取性别为男性的并且年龄是24岁
    List<Student> collect = data.stream().filter(student -> "man".equals(student.getSex()) && "24".equals(student.getAge())).collect(Collectors.toList());
    System.out.println(collect);
    // 控制台输出
    // [Student{id='3', name='lisi', sex='man', age='24'}]
    
  • map类型映射

    // 取出所有用户的名字
    // 写法1:
    List<String> collect = data.stream().map(student -> student.getName()).collect(Collectors.toList());
    // 写法2:
    List<String> collect = data.stream().map(Student::getName).collect(Collectors.toList());
    System.out.println(collect);
    // 控制台输出
    [xiaoming, wangwu, lisi, xiyangyang, zhangsan, xiaohong]
    
  • distinct去重操作

    List<String> list = new ArrayList<>();
    list.add("apple");
    list.add("banana");
    list.add("orange");
    list.add("pear");
    list.add("dog");
    list.add("cat");
    list.add("apple");
    list.add("apple");
    list.add("apple");
    List<String> collect = list.stream().distinct().collect(Collectors.toList());
    System.out.println(collect);
    // 控制台输出
    [apple, banana, orange, pear, dog, cat]
    
  • flatMap平铺数据元素,通俗来讲,就是将两个list中的数据合到一个大的list中,并且这些元素都是展开来的

    // 假设我们有一些数据
    List<String> teamIndia = Arrays.asList("Virat", "Dhoni", "Jadeja");
    List<String> teamAustralia = Arrays.asList("Warner", "Watson", "Smith");
    List<String> teamEngland = Arrays.asList("Alex", "Bell", "Broad");
    List<String> teamNewZeland = Arrays.asList("Kane", "Nathan", "Vettori");
    List<String> teamSouthAfrica = Arrays.asList("AB", "Amla", "Faf");
    List<String> teamWestIndies = Arrays.asList("Sammy", "Gayle", "Narine");
    List<String> teamSriLanka = Arrays.asList("Mahela", "Sanga", "Dilshan");
    List<String> teamPakistan = Arrays.asList("Misbah", "Afridi", "Shehzad");
    
    List<List<String>> playersInWorldCup2016 = new ArrayList<>();
    playersInWorldCup2016.add(teamIndia);
    playersInWorldCup2016.add(teamAustralia);
    playersInWorldCup2016.add(teamEngland);
    playersInWorldCup2016.add(teamNewZeland);
    playersInWorldCup2016.add(teamSouthAfrica);
    playersInWorldCup2016.add(teamWestIndies);
    playersInWorldCup2016.add(teamSriLanka);
    playersInWorldCup2016.add(teamPakistan);
    System.out.println(playersInWorldCup2016);
    // 这时控制台打印是这样的
    [[Virat, Dhoni, Jadeja], [Warner, Watson, Smith], [Alex, Bell, Broad], [Kane, Nathan, Vettori], [AB, Amla, Faf], [Sammy, Gayle, Narine], [Mahela, Sanga, Dilshan], [Misbah, Afridi, Shehzad]]
    
    // 我们用flatMap平铺开来
    List<String> collect = playersInWorldCup2016.stream().flatMap(plist -> plist.stream()).collect(Collectors.toList());
    System.out.println(collect);
    // 得到的结果是
    [Virat, Dhoni, Jadeja, Warner, Watson, Smith, Alex, Bell, Broad, Kane, Nathan, Vettori, AB, Amla, Faf, Sammy, Gayle, Narine, Mahela, Sanga, Dilshan, Misbah, Afridi, Shehzad]
    
    
  • skip跳过元素,limit获取n个元素

    // 跳过前4个元素
    List<String> list = new ArrayList<>();
    

list.add(“apple”);
list.add(“banana”);
list.add(“orange”);
list.add(“pear”);
list.add(“dog”);
list.add(“cat”);
list.add(“apple”);
list.add(“apple”);
list.add(“apple”);

List collect = list.stream().skip(4).collect(Collectors.toList());
System.out.println(collect);

// 结果是:[dog, cat, apple, apple, apple]


```java
// 只取前四个
List collect = list.stream().limit(4).collect(Collectors.toList());
System.out.println(collect);
// 结果是:[apple, banana, orange, pear]
// 另外,还有一个小技巧,skip和limit配合使用可以实现分页
// limit配合skip实现分页
List<String> list = new ArrayList<>();
for (int i = 1; i <= 20; i++) {
    list.add("数据"+i);
}
// 每页大小
int pageSize = 10;
// 页码
int pageIndex = 1;
List<String> collect = list.stream().skip((pageIndex - 1) * pageSize).limit(pageSize).collect(Collectors.toList());
System.out.println(collect);
// 结果:[数据1, 数据2, 数据3, 数据4, 数据5, 数据6, 数据7, 数据8, 数据9, 数据10]
  • sorted排序

    // 实体类
    public class Person implements Comparable<Person>{
        private Integer id;
        private String name;
        private String sex;
        private Integer age;
    	
        // get,set...
        
        @Override
        public int compareTo(Person o) {
            return this.age - o.getAge();
        }
    }
    
    // 第一种方式:sorted()方法不给参数,在实体类里实现Comparable比较接口,重新比较方法
    List<Person> list = new ArrayList<>();
    Person p1 = new Person(1,"xiaoming","man",23);
    Person p2 = new Person(2,"xiaohong","woman",27);
    Person p3 = new Person(1,"xiaolei","man",24);
    Person p4 = new Person(1,"xiaoli","man",26);
    list.add(p1);
    list.add(p2);
    list.add(p3);
    list.add(p4);
    List<Person> collect = list.stream().sorted().collect(Collectors.toList());
    
    // 第二种方式:直接在sorted方法里给定参数
    List<Person> collect = list.stream()
        .sorted((Comparator.comparingInt(Person::getAge)))
        .collect(Collectors.toList());
    // 结果:
    [Person{id=1, name='xiaoming', sex='man', age=23}, 
     Person{id=1, name='xiaolei', sex='man', age=24}, 
     Person{id=1, name='xiaoli', sex='man', age=26}, 
     Person{id=2, name='xiaohong', sex='woman', age=27}]
    
    // 若要倒序,只需加上reversed方法即可
    List<Person> collect = list.stream().sorted((Comparator.comparingInt(Person::getAge)).reversed())
        .collect(Collectors.toList());
    
  • peek处理元素内数据

    // peek()方法存在的主要目的是用调试,通过peek()方法可以看到流中的数据经过每个处理点时的状态
    List<Person> collect = list.stream().peek(person -> System.out.println("[person]=" + person)).collect(Collectors.toList());
    // 输出结果:
    [person]=Person{id=1, name='xiaoming', sex='man', age=23}
    [person]=Person{id=2, name='xiaohong', sex='woman', age=27}
    [person]=Person{id=1, name='xiaolei', sex='man', age=24}
    [person]=Person{id=1, name='xiaoli', sex='man', age=26}
    // 和forEach不同的是,peek是中间操作,而forEach是终止操作,终止操作只能有一个
    
    // 除去用于调试,peek()在需要修改元素内部状态的场景也非常有用,比如我们想将所有Person的名字修改为大写
    List<Person> collect = list.stream().peek(person -> {
        person.setName(person.getName().toUpperCase());
    }).collect(Collectors.toList());
    // 输出结果:
    [Person{id=1, name='XIAOMING', sex='man', age=23}, Person{id=2, name='XIAOHONG', sex='woman', age=27}, Person{id=1, name='XIAOLEI', sex='man', age=24}, Person{id=1, name='XIAOLI', sex='man', age=26}]
    
  • count统计

    // 统计苹果出现的次数
    List<String> list = new ArrayList<>();
    list.add("apple");
    list.add("banana");
    list.add("orange");
    list.add("pear");
    list.add("dog");
    list.add("cat");
    list.add("apple");
    list.add("apple");
    list.add("apple");
    long apple = list.stream().filter(s -> "apple".equals(s)).count();
    // 另外上面还可以替换为
    long apple = list.stream().filter("apple"::equals).count();
    System.out.println(apple);
    // 结果是:4
    
  • findfirst查找第一个符合条件的元素

    List<Person> list = new ArrayList<>();
    Person p1 = new Person(1,"xiaoming","man",23);
    Person p2 = new Person(2,"xiaohong","woman",27);
    Person p3 = new Person(1,"xiaolei","man",24);
    Person p4 = new Person(1,"xiaoli","man",26);
    Person p5 = new Person(1,"xiaomeng","man",23);
    list.add(p1);
    list.add(p2);
    list.add(p3);
    list.add(p4);
    list.add(p5);
    Person person1 = list.stream().filter(person -> person.getAge() == 23).findFirst().get();
    System.out.println(person1);
    // 结果:Person{id=1, name='xiaoming', sex='man', age=23}
    
  • findAny查找任意一个符合条件的元素,实际上,findAny和findFirst返回的,都是第一个对象,在有序的集合中,他们使用起来并没有什么区别。但是在并发流或者无序集合下,并不保证返回的都是第一个元素

    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    
    Optional<Integer> any = list.stream().filter(x -> x > 1).findAny();
    if (any.isPresent()) {
        Integer result = any.get();
        System.out.println(result);
    }
    // 结果:任何一个大于1的数字都有可能
    
  • *match 匹配操作

    List<String> list = new ArrayList<>();
    list.add("apple");
    list.add("banana");
    list.add("orange");
    list.add("pear");
    list.add("dog");
    list.add("cat");
    list.add("apple");
    list.add("apple");
    list.add("apple");
    // anyMatch表示,在判断的条件里,匹配任意一个元素返回true
    boolean b = list.stream().anyMatch(s -> "cat".equals(s));
    System.out.println(b);
    // 结果是: true
    
    // allMatch表示,判断的条件里,所有的元素都是,返回true
    // 结果是: false
    boolean b = list.stream().allMatch(s -> "cat".equals(s));
    
    // noneMatch跟allMatch相反,判断条件里的元素,所有的都不是,返回true
    // 结果是:false
    boolean b = list.stream().noneMatch(s -> "applesss".equals(s));
    
  • min,max,sum求值

    List<Person> list = new ArrayList<>();
    Person p5 = new Person(1,"xiaomeng","man",23);
    Person p1 = new Person(1,"xiaoming","man",23);
    Person p2 = new Person(2,"xiaohong","woman",27);
    Person p3 = new Person(1,"xiaolei","man",24);
    Person p4 = new Person(1,"xiaoli","man",26);
    
    list.add(p1);
    list.add(p2);
    list.add(p3);
    list.add(p4);
    list.add(p5);
    // 求最大值
    Person person = list.stream().max(Comparator.comparing(Person::getAge)).get();
    System.out.println(person);
    // 结果是:Person{id=2, name='xiaohong', sex='woman', age=27}
    
    // 求最小值
    Person person = list.stream().min(Comparator.comparing(Person::getAge)).get();
    // 结果是:Person{id=1, name='xiaoming', sex='man', age=23}
    
    
    // 当然,还可以求平均值,求和,不过得借助中间操作mapToInt
    
    // 求最大值
    // 结果是27
    int asInt = list.stream().mapToInt(Person::getAge).max().getAsInt();
    
    // 求最小值
    // 结果是23
    int asInt = list.stream().mapToInt(Person::getAge).min().getAsInt();
    
    // 求平均值
    // 结果是24.6
    double asInt = list.stream().mapToInt(Person::getAge).average().getAsDouble();
    
    // 求和
    // 结果是123
    int sum = list.stream().mapToInt(Person::getAge).sum();
    
  • reduce方法用到得较少,这里不做过多研究,请参考:https://www.jianshu.com/p/3b0fbcc9f24d

  • forEach遍历

    // forEach用来遍历元素
    list.stream().forEach(person -> System.out.println(person));
    // 也可以简写为:
    list.stream().forEach(System.out::println);
    // 结果是:
    Person{id=1, name='xiaoming', sex='man', age=23}
    Person{id=2, name='xiaohong', sex='woman', age=27}
    Person{id=3, name='xiaolei', sex='man', age=24}
    Person{id=4, name='xiaoli', sex='man', age=26}
    Person{id=5, name='xiaomeng', sex='man', age=23}
    

    另外还有一个forEachOrdered方法

    // 主要的区别在并行流的处理上
    // 输出的顺序不一定(效率更高)
    Stream.of("AAA", "BBB", "CCC").parallel().forEach(s -> System.out.println("Output:" + s));
    // 输出的顺序与元素的顺序严格一致
    Stream.of("AAA", "BBB", "CCC").parallel().forEachOrdered(s -> System.out.println("Output:" + s));
    
  • toArray转换为数组

    // 这个方法就是将数据流中的数据转换为一个数组
    Person[] people = list.stream().toArray(Person[]::new);
    // 单独用这个方法没什么意思,在idea中提示可以简化方法
    Person[] people = list.toArray(new Person[0]);
    
  • collect收集操作

    • 经过前面的一系列数据处理后,需要将数据收集起来,collect就是这个收集器操作

      // 提供两种不同参数的重载方法
      // 第一种传入3个参数
      <R> R collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner);
      // 第二种传入一个Collectors类的静态方法
      <R, A> R collect(Collector<? super T, A, R> collector);
      
    • 先看一下Collectors静态工厂类,这个类包含了很多静态方法,用起来非常方便

      方法 返回值 说明
      toList() List 把流中的数据收集到一个List中
      toSet() Set 把流中的数据收集到一个Set中,并去重
      toCollection() Collection 把流中的数据收集到原始类型集合中,可以指定具体集合类型
      toMap() Collector> 收集为Map类型
      toConcurrentMap() Collector> 收集为ConcurrentMap类型
      counting Long 计算流中元素的个数
      summingInt() Integer 对流中的元素整数属性求和
      averagingInt() Double 对流中元素的Integer类型求平均值,返回Double类型
      summarizingInt() IntSummary… 对流中元素Integer类型进行求最大,最小,平均值,是个综合求值操作
      joining() String 连接流中元素,并产生成一个字符串,可指定分隔符
      maxBy Optional 选择流中最大元素,按照比较器指定的排序字段
      minBy Optional 选择流中最小元素,按照比较器指定的排序字段
      reducing T 规约操作,指定初始值,然后规约某个字段为一个值
      collectingAndThen T 收集之后,对其结果进行操作
      groupingBy Map> 将流中的元素进行分组
      partitioningBy Map> 将流中的元素进行分区
     // toList()
     // 在上面的例子中已经出现了不少次,没错他就是将结果收集为List类型的集合
     // 这个方法的底层源码默认采用的集合类型是ArrayList
     List<Person> collect = list.stream().sorted().collect(Collectors.toList());
    
    // toSet()
    // 将结果收集为Set集合类型,默认实现是HashSet类型,转换为Set集合时,数据会去重
    // 产生的数据是无序的
    Set<Person> collect = data.stream().limit(4).collect(Collectors.toSet());
    
    // toCollection()
    // 将数据收集为集合类型,可以指定集合类型
    ArrayList<Person> collect = data.stream().limit(4).collect(Collectors.toCollection(ArrayList::new));
    
    // toMap()
    // 将数据收集为Map类型,参数必须符合K-V类型,其中Value可以为某个字段,也可以是该对象本身
    // Function.identity() 返回的就是该对象本身,其源码注释有这么一句话:returns a function that always returns its input argument.意思为:始终返回其输入参数类型
    Map<Integer, Person> collect = data.stream().collect(Collectors.toMap(Person::getId, Function.identity()));
    
    // k为id,v为某个字段
    Map<Integer, String> collect = data.stream().collect(Collectors.toMap(Person::getId, Person::getName));
    // 打印结果:{1=xiaoming, 2=xiaohong, 3=xiaolei, 4=xiaoli, 5=xiaomeng}
    
    // 那么如果key重复的该怎么处理?这里我们假设有两个id相同Person,如果他们id相同,在转成Map的时候,取age大一个,小的将会被丢弃。
    List<Person> data = new ArrayList<>();
    Person p6 = new Person(1,"xiaoming","man",24);
    Person p5 = new Person(5,"xiaomeng","man",23);
    Person p1 = new Person(1,"xiaoming","man",23);
    Person p2 = new Person(2,"xiaohong","woman",27);
    Person p3 = new Person(3,"xiaolei","man",24);
    Person p4 = new Person(4,"xiaoli","man",26);
    data.add(p1);
    data.add(p2);
    data.add(p3);
    data.add(p4);
    data.add(p5);
    data.add(p6);
    Map<Integer, Person> collect = data.stream().collect(Collectors.toMap(Person::getId, Function.identity(), BinaryOperator.maxBy(Comparator.comparing(Person::getAge))));
    // 结果为:{1=Person{id=1, name='xiaoming', sex='man', age=24}, 2=Person{id=2, name='xiaohong', sex='woman', age=27}, 3=Person{id=3, name='xiaolei', sex='man', age=24}, 4=Person{id=4, name='xiaoli', sex='man', age=26}, 5=Person{id=5, name='xiaomeng', sex='man', age=23}}
    

你可能感兴趣的:(Java,stream,java)