Java 8 Stream

本文详细介绍了Java 8的Stream API,包括其概念、特点、生成方式以及常用操作如reduce、peek、forEach、map、flatMap、filter、limit、sorted等。还探讨了终端操作如Collectors的使用,如归类、统计、转换等,并通过实例展示了如何在实际编程中高效利用Stream API进行数据处理。

序言

       强化下Stream,现在越来越多的框架和工具在使用类似的特性作为自己的语言了。其实主要是熟悉java8所提供的中间方法和最终方法。这些方法在python spark,flink,中都会看到,所以是个很重要的一个变成风格,且相互之间肯定有关联的,对于学习其它框架会有很大的帮助cuiyaonan2000@163.com

简介

Stream这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

什么是 Stream?

Stream(流)是一个来自数据源元素队列并支持聚合操作

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
  • 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。-------------这个是熟悉的重点cuiyaonan2000@163.COM

和以前的Collection操作不同, Stream操作还有两个基础的特征:

  • Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
  • 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

生成流的方式

在 Java 8 中, 集合接口有两个方法来生成流:

  • stream() − 为集合创建串行流。

  • parallelStream() − 为集合创建并行流

reduce

reduce的作用就是遍历集合,计算累加每个元素的值,然后返回一个对象. 这里的元素不是一定要数字,也可以是对象.因为计算方法是我们自己写的.

因为是累加所以就会有中间结果值和初始值的设置.其它的没人什么cuiyaonan2000@163.com

package nan.yao.cui.others;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collector;
import java.util.stream.Collectors;

/**
 * @Author: cuiyaonan2000@163.com
 * @Description: todo
 * @Date: Created at 2023-1-6  8:56
 */
public class Test4 {

    public static void main(String[] args){


        List<Integer> sleepList = new ArrayList();

        sleepList.add(1);
        sleepList.add(2);
        sleepList.add(3);

        //如下 a 跟 b这2个参数 .a 是中间结果,即上一个a+b的结果,b是List中的轮询数据. a第一次取值为List的第一个元素
        System.out.println(sleepList.stream().reduce((a,b)->a+b).toString());

        //这里的10 是第一次运行的a的值初始值.而不是从List中获取值
        System.out.println(sleepList.stream().reduce(10,(a,b)->a+b).toString());

        //同上temList即最终的结果的类型,加初始化A的值
        //a 就是temList,b是List的循环元素
        //a1 b2就是parallel()多线程的结果计算返回.那这里的result感觉就是个废物.
        List<Integer> temList = new ArrayList<>();
        Integer result =0;
         Integer aa = sleepList.stream().parallel().reduce(result,(a,b)->{
            return a+b;
        },(a1,b1)->{
             return a1+b1;
         });
         System.out.println(temList.size());
        System.out.println(aa);
    }



}

peek

peek主要用于对遍历的数据进行一些操作,但是对数据影响并不会改变原来的的值,因为修改后的值不会返还给流中. 所以主要是用来遍历流中的对象.(也有特例如果我们遍历的是  对象 即引用,则会改变.cuiyaonan2000@163.com)

另外特别注意peek是中间操作,如果在它后面没有跟最终操作,则该peek中的方法不会运行cuiyaonan2000@163.com

通过peek的入参是Consumer,Map的入参是Function

  • Consumer是没有返回值的,它只是对Stream中的元素进行某些操作,但是操作之后的数据并不返回到Stream中,所以Stream中的元素还是原来的元素。
  • 而Function是有返回值的,这意味着对于Stream的元素的所有操作都会作为新的结果返回到Stream中。
Stream.of("one", "two", "three","four").peek(u -> u.toUpperCase())
                .forEach(System.out::println);
展示结果:

one
two
three
four



Stream.of("one", "two", "three","four").map(u -> u.toUpperCase())
                .forEach(System.out::println);

展示结果:

ONE
TWO
THREE
FOUR

forEach

用于遍历集合,不会返回内容。我们可对数据进行显示和计算的操作。

Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数:

Random random = new Random();
random.ints().limit(10).forEach(System.out::println);

map

该方法表示将 队列中的或者流中的对象进行处理,返回一个新的对象(当然也可以返回原来的对象,对比foreach就是每个对象处理完了,有另外一个对象返回cuiyaonan2000@163.com)

map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// 获取对应的平方数
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());

flatMap

flatmap与map的区别就是:flatmap会在过滤集合中的集合对象中时,按照一个集合放回。map则会把集合中的集合原样返回。

       List<String[]> eggs = new ArrayList<>();


        // 第一箱鸡蛋
        eggs.add(new String[]{"鸡蛋_1", "鸡蛋_1", "鸡蛋_1", "鸡蛋_1", "鸡蛋_1"});
        // 第二箱鸡蛋
        eggs.add(new String[]{"鸡蛋_2", "鸡蛋_2", "鸡蛋_2", "鸡蛋_2", "鸡蛋_2"});
 

        eggs.stream()
                .map(x -> Arrays.stream(x).map(y -> y.replace("鸡", "煎")))
                .forEach(x -> System.out.println("组" + group++ + ":" + Arrays.toString(x.toArray())));

        /*
        控制台打印:------------
        组1:[煎蛋_1, 煎蛋_1, 煎蛋_1, 煎蛋_1, 煎蛋_1]
        组2:[煎蛋_2, 煎蛋_2, 煎蛋_2, 煎蛋_2, 煎蛋_2]
         */



        eggs.stream()
                .flatMap(x -> Arrays.stream(x).map(y -> y.replace("鸡", "煎")))
                .forEach(x -> System.out.println("学生" + student++ + ":" + x));
        /*
        控制台打印:------------
        学生1:煎蛋_1
        学生2:煎蛋_1
        学生3:煎蛋_1
        学生4:煎蛋_1
        学生5:煎蛋_1
        学生6:煎蛋_2
        学生7:煎蛋_2
        学生8:煎蛋_2
        学生9:煎蛋_2
        学生10:煎蛋_2
         */

filter

该方法是过滤,与map的区别是:map每个对象都会返回,filter只返回满足条件的

filter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤出空字符串:

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
long count = strings.stream().filter(string -> string.isEmpty()).count();

limit

用于控制队列或者流中的,数据项数量

limit 方法用于获取指定数量的流。 以下代码片段使用 limit 方法打印出 10 条数据:

Random random = new Random();
random.ints().limit(10).forEach(System.out::println);

sorted

这个就是将队列或者流中的数据项进行排序。

根据什么排序呢,如果是对象可以使用Comparator.comparing(Person::getAge),其中Person是类,getAge是方法。用getAge的返回内容进行排序升序,使用.reversed()来进行逆序cuiyaonan2000@163.com

//sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法对输出的 10 个随机数进行排序:
Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);


//自然排序 升序
List<Person> listTem = list.stream().sorted() 
//逆序
List<Person> listTem = list.stream().sorted(Comparator.reverseOrder()) 


//升序
List<Person> listTem = list.stream().sorted(Comparator.comparing(Person::getAge)) 

//逆序
List<Person> listTem = list.stream().sorted(Comparator.comparing(Person::getAge).reversed()) 

终端操作

Collectors---------结果返回

这个就是将我们处理过后的数组或者流的结果返回。注意该写法。另外以上实例中的count()等方法,其实就是集合或者流的统计方法,针对的是整体结果集cuiyaonan2000@163.com

Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
 
System.out.println("筛选列表: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);

Collectors的其它方法(也可以自定义Collector,看需求)

首先归类操作已经停了很多有用的.

统计

另外,一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果。

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
 
IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
 
System.out.println("列表中最大的数 : " + stats.getMax());
System.out.println("列表中最小的数 : " + stats.getMin());
System.out.println("所有数之和 : " + stats.getSum());
System.out.println("平均数 : " + stats.getAverage());

anyMatch,allMatch,noneMatch,count

  1. anyMatch表示,判断的条件里,任意一个元素成功,返回true
  2. allMatch表示,判断条件里的元素,所有的都是,返回true
  3. noneMatch跟allMatch相反,判断条件里的元素,所有的都不是,返回true
  4. count:统计结果流中的对象个数
List<String> strs = Arrays.asList("a", "a", "a", "a", "b");

boolean aa = strs.stream().anyMatch(str -> str.equals("a"));
boolean bb = strs.stream().allMatch(str -> str.equals("a"));
boolean cc = strs.stream().noneMatch(str -> str.equals("a"));

long count = strs.stream().filter(str -> str.equals("a")).count();

System.out.println(aa);// TRUE
System.out.println(bb);// FALSE
System.out.println(cc);// FALSE
System.out.println(count);// 

Collectors.groupingBy

顾名思义就是将流中的对象进行分组,返回一个Map集合.其中可以根据需要来选择key 和value.示例如下所示:

Map<Integer,Integer> map = Map.of(1,23,12,11);


List<Student> list = List.of(new Student("1",132),
               new Student("1",131));


Map result = list.parallelStream().collect(Collectors.groupingBy(e->e.name
               ,Collectors.mapping(Student::age,Collectors.toSet())));
       


//如此这般,result 的key就是student的name , vlaue就是student的set结合包含了age
System.out.println(result);



//studeng的声明如下所示

 public record Student(String name,int age){

    }

如果多线程的话可以使用 groupingByConcurrent 接收子线程的结果,以并发执行的方式,而不需要一个一个接收子线程的结果,以此来提升工作效率cuiyaonan2000@163.com

Collectors.toMap

同理toMap也提供了多种方式应对各种场景,所以自定义的Collectors是否有必要,另外这里要注意的是如果key有重复的 且没有指明处理方式的时候会报错,但是如果指明了当key如何处理value的函数(即下方的 (s,a)-> Integer.valueOf((s+a)) ,则不会报错cuiyaonan2000@163.com

 public static void main(String[] args){

       List<Student> list = List.of(new Student("1",132),
               new Student("1",131));


        result = list.parallelStream().collect(Collectors.toMap(Student::name,Student::age,
                (s,a)-> Integer.valueOf((s+a))));

        //这样子如果key相同的话 就会累加age 并返回
        System.out.println(result);
    }


    public record Student(String name,int age){

    }

同理也提供了以并行的方式收集子线程结果的方法

 

Collectors.mapping

Collectors.mapping(Student::age,Collectors.toSet())

Adapts a Collector accepting elements of type U to one accepting elements of type T by applying a mapping function to each input element before accumulation.


Params:
mapper – a function to be applied to the input elements

downstream – a collector which will accept mapped values


Returns:
a collector which applies the mapping function to the input elements and provides the mapped results to the downstream collector


API Note:
The mapping() collectors are most useful when used in a multi-level reduction, such as downstream of a groupingBy or partitioningBy.

For example, given a stream of Person, to accumulate the set of last names in each city:


 Map<City, Set<String>> lastNamesByCity    = people.stream().collect(      groupingBy(Person::getCity,                 mapping(Person::getLastName,                         toSet())));

如上 Collector.mapping 跟我们使用map()方法一样就是遍历一个对象,然后返还一个对象.上面说的是非常适合跟groupby配合使用的.

//Map<Integer,Integer> map = Map.of(1,23,12,11);

List<Student> list = List.of(new Student("1",132),
               new Student("1",131));

//list.stream().collect(Collectors.toList());

Map result = list.parallelStream().collect(Collectors.groupingBy(e->e.name
               ,Collectors.mapping(student -> student,Collectors.toMap(Student::name,Student::age))));

Collectors.partitioningBy

JDK自己提供的Collector 很多都可以是设置 结果集合的类型,当然了这里我们自己提供的集合类型可以是Map,List,Set,也可以使用JDK自带的Collector中的toList(),toSet(),toMap()等集合cuiyaonan2000@163.com  这里的partitionBy也不例外

 

partitioningBy 在遍历集合的时候根据断言 判断是放到Map的true 或者 false 的key对应的结合当中.

结果的格式为: Map<Boolean, D>

List<Student> list = List.of(new Student("1",132),
               new Student("1",131));

Map tempResult = null;

tempResult = list.stream().collect(Collectors.partitioningBy(e->e.age>1, Collectors.toList()));

 

collectingAndThen

顾明思议就是先把结果收集后 在进行处理. 

方法的定义如下所示:

 /* @param <T> the type of the input elements
     * @param <A> intermediate accumulation type of the downstream collector
     * @param <R> result type of the downstream collector
     * @param <RR> result type of the resulting collector
     * @param downstream a collector
     * @param finisher a function to be applied to the final result of the downstream collector
     * @return a collector which performs the action of the downstream collector,
     * followed by an additional finishing step
     */


    public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream,
                                                                Function<R,RR> finisher) 

完整实例

import java.util.ArrayList;
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.Map;
 
public class Java8Tester {
   public static void main(String args[]){
      System.out.println("使用 Java 7: ");
        
      // 计算空字符串
      List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
      System.out.println("列表: " +strings);
      long count = getCountEmptyStringUsingJava7(strings);
        
      System.out.println("空字符数量为: " + count);
      count = getCountLength3UsingJava7(strings);
        
      System.out.println("字符串长度为 3 的数量为: " + count);
        
      // 删除空字符串
      List<String> filtered = deleteEmptyStringsUsingJava7(strings);
      System.out.println("筛选后的列表: " + filtered);
        
      // 删除空字符串,并使用逗号把它们合并起来
      String mergedString = getMergedStringUsingJava7(strings,", ");
      System.out.println("合并字符串: " + mergedString);
      List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
        
      // 获取列表元素平方数
      List<Integer> squaresList = getSquares(numbers);
      System.out.println("平方数列表: " + squaresList);
      List<Integer> integers = Arrays.asList(1,2,13,4,15,6,17,8,19);
        
      System.out.println("列表: " +integers);
      System.out.println("列表中最大的数 : " + getMax(integers));
      System.out.println("列表中最小的数 : " + getMin(integers));
      System.out.println("所有数之和 : " + getSum(integers));
      System.out.println("平均数 : " + getAverage(integers));
      System.out.println("随机数: ");
        
      // 输出10个随机数
      Random random = new Random();
        
      for(int i=0; i < 10; i++){
         System.out.println(random.nextInt());
      }
        
      System.out.println("使用 Java 8: ");
      System.out.println("列表: " +strings);
        
      count = strings.stream().filter(string->string.isEmpty()).count();
      System.out.println("空字符串数量为: " + count);
        
      count = strings.stream().filter(string -> string.length() == 3).count();
      System.out.println("字符串长度为 3 的数量为: " + count);
        
      filtered = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.toList());
      System.out.println("筛选后的列表: " + filtered);
        
      mergedString = strings.stream().filter(string ->!string.isEmpty()).collect(Collectors.joining(", "));
      System.out.println("合并字符串: " + mergedString);
        
      squaresList = numbers.stream().map( i ->i*i).distinct().collect(Collectors.toList());
      System.out.println("Squares List: " + squaresList);
      System.out.println("列表: " +integers);
        
      IntSummaryStatistics stats = integers.stream().mapToInt((x) ->x).summaryStatistics();
        
      System.out.println("列表中最大的数 : " + stats.getMax());
      System.out.println("列表中最小的数 : " + stats.getMin());
      System.out.println("所有数之和 : " + stats.getSum());
      System.out.println("平均数 : " + stats.getAverage());
      System.out.println("随机数: ");
        
      random.ints().limit(10).sorted().forEach(System.out::println);
        
      // 并行处理
      count = strings.parallelStream().filter(string -> string.isEmpty()).count();
      System.out.println("空字符串的数量为: " + count);
   }
    
   private static int getCountEmptyStringUsingJava7(List<String> strings){
      int count = 0;
        
      for(String string: strings){
        
         if(string.isEmpty()){
            count++;
         }
      }
      return count;
   }
    
   private static int getCountLength3UsingJava7(List<String> strings){
      int count = 0;
        
      for(String string: strings){
        
         if(string.length() == 3){
            count++;
         }
      }
      return count;
   }
    
   private static List<String> deleteEmptyStringsUsingJava7(List<String> strings){
      List<String> filteredList = new ArrayList<String>();
        
      for(String string: strings){
        
         if(!string.isEmpty()){
             filteredList.add(string);
         }
      }
      return filteredList;
   }
    
   private static String getMergedStringUsingJava7(List<String> strings, String separator){
      StringBuilder stringBuilder = new StringBuilder();
        
      for(String string: strings){
        
         if(!string.isEmpty()){
            stringBuilder.append(string);
            stringBuilder.append(separator);
         }
      }
      String mergedString = stringBuilder.toString();
      return mergedString.substring(0, mergedString.length()-2);
   }
    
   private static List<Integer> getSquares(List<Integer> numbers){
      List<Integer> squaresList = new ArrayList<Integer>();
        
      for(Integer number: numbers){
         Integer square = new Integer(number.intValue() * number.intValue());
            
         if(!squaresList.contains(square)){
            squaresList.add(square);
         }
      }
      return squaresList;
   }
    
   private static int getMax(List<Integer> numbers){
      int max = numbers.get(0);
        
      for(int i=1;i < numbers.size();i++){
        
         Integer number = numbers.get(i);
            
         if(number.intValue() > max){
            max = number.intValue();
         }
      }
      return max;
   }
    
   private static int getMin(List<Integer> numbers){
      int min = numbers.get(0);
        
      for(int i=1;i < numbers.size();i++){
         Integer number = numbers.get(i);
        
         if(number.intValue() < min){
            min = number.intValue();
         }
      }
      return min;
   }
    
   private static int getSum(List numbers){
      int sum = (int)(numbers.get(0));
        
      for(int i=1;i < numbers.size();i++){
         sum += (int)numbers.get(i);
      }
      return sum;
   }
    
   private static int getAverage(List<Integer> numbers){
      return getSum(numbers) / numbers.size();
   }
}

返回的内容

$ javac Java8Tester.java 
$ java Java8Tester
使用 Java 7: 
列表: [abc, , bc, efg, abcd, , jkl]
空字符数量为: 2
字符串长度为 3 的数量为: 3
筛选后的列表: [abc, bc, efg, abcd, jkl]
合并字符串: abc, bc, efg, abcd, jkl
平方数列表: [9, 4, 49, 25]
列表: [1, 2, 13, 4, 15, 6, 17, 8, 19]
列表中最大的数 : 19
列表中最小的数 : 1
所有数之和 : 85
平均数 : 9
随机数: 
-393170844
-963842252
447036679
-1043163142
-881079698
221586850
-1101570113
576190039
-1045184578
1647841045
使用 Java 8: 
列表: [abc, , bc, efg, abcd, , jkl]
空字符串数量为: 2
字符串长度为 3 的数量为: 3
筛选后的列表: [abc, bc, efg, abcd, jkl]
合并字符串: abc, bc, efg, abcd, jkl
Squares List: [9, 4, 49, 25]
列表: [1, 2, 13, 4, 15, 6, 17, 8, 19]
列表中最大的数 : 19
列表中最小的数 : 1
所有数之和 : 85
平均数 : 9.444444444444445
随机数: 
-1743813696
-1301974944
-1299484995
-779981186
136544902
555792023
1243315896
1264920849
1472077135
1706423674
空字符串的数量为: 2

### Java 8 Stream API 使用教程 #### 创建流 创建流的方式有多种,可以通过集合类的 `stream()` 方法获取流实例。 ```java List<String> list = Arrays.asList("apple", "banana", "orange"); Stream<String> stream = list.stream(); ``` 对于数组,则可以使用静态方法 `Arrays.stream` 或者 `Stream.of`. ```java String[] array = {"apple", "banana", "orange"}; Stream<String> streamFromArray = Arrays.stream(array); // or Stream<String> streamOfArray = Stream.of(array); ``` #### 中间操作 中间操作返回一个新的流对象,在此之后还可以继续调用其他的操作。常见的中间操作包括但不限于: - **filter**: 过滤掉不符合条件的元素[^1] ```java Stream<String> filteredStream = stream.filter(s -> s.startsWith("a")); ``` - **map**: 将每一个输入元素按照指定映射关系转换成另一个输出元素 ```java Stream<Integer> lengthStream = stream.map(String::length); ``` - **peek**: 对每个元素执行某些动作并传给下游,通常用于调试目的[^2] ```java Stream<String> peekedStream = stream.peek(System.out::println); ``` #### 终端操作 终端操作会触发实际计算过程,并关闭流。一旦执行了一个终端操作后就不能再对该流做任何进一步的操作了。一些常用的终端操作如下所示: - **forEach**: 遍历整个序列中的每一项 ```java stream.forEach(System.out::println); ``` - **collect(Collectors.toList())**: 收集结果到列表中 ```java List<String> resultList = stream.collect(Collectors.toList()); ``` - **count()**: 计算总数目 ```java long count = stream.count(); ``` 这些只是基础部分;实际上还有更多高级特性和组合方式等待探索。通过合理运用上述概念以及所提供的例子,应该能更好地理解如何利用Java Streams简化日常开发工作流程的同时提高效率和代码质量。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cuiyaonan2000

给包烟抽吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值