飞道的博客

Java 8 Stream常用方法学习

459人阅读  评论(0)

Stream基础概念

Stream流是 Java8 API 新增的一个处理集合的关键抽象概念,是一个来自数据源的元素队列并支持聚合操作。以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

相关名词 描述
元素 对象形成的一个队列。 Java中的Stream并不会存储元素,而是按需计算
数据源 是流Stream的来源。 可以是集合、数组、I/O channel、 产生器generator 等
聚合操作 类似SQL语句一样的操作,比如filter, map, reduce, find, match, sorted等。
内部迭代 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
Pipelining 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现

Stream的特点:

  1. Stream不是什么数据结构,它不会保存数据,只是将操作的数据结果保存到另一个对象中。
  2. Stream是惰性求值的(延迟执行),在中间处理的过程中,只对操作进行记录,并不会立即执行,只有等到执行终止操作的时候才会进行实际的计算,这时候中间操作才会执行。
  3. 可以把Stream当成一个高级版本的Iterator来使用。(原始版本的Iterator,只能一个个的遍历操作)

1. 分类

Stream操作分类 作用描述 常用方法
无状态 中间操作, 该操作不受之前元素的影响 unordered、filter、map、mapToInt、mapToLong、mapToDouble、flatMap、flatMapToInt、flatMapToLongg、flatMapToDouble、peek
有状态 中间操作,该操作只有拿到所有元素之后才能继续执行 distinct、sorted、limit、skip
非短路操作 结束操作 , 必须处理所有元素才能得到最终结果 forEach、forEachOrdered、toArray、collect、max、min、count、reduce
短路操作 结束操作 , 遇到某些符合条件的元素就可以得到最终结果 anyMatch、allMatch、noneMatch、findFirsh、findAny

2. 常用方法

初始化一个String类型的List集合

List<String> stringList = Arrays.asList("a","b", "", "", "c", "", "a", "d","e","a");
System.out.println("字符串集合:" + stringList);

运行结果:

字符串集合:[a, b, , , c, , a, d, e, a]

创建JavaBean对象User,初始化一个User列表

@Data
class User{
   
    String name;
    String description;
}
List<User> userList = new ArrayList<>(
  Arrays.asList(
      new User("strive", "努力"),
      new User("fighter", "奋斗"),
      new User("lucky",  "幸运"),
      new User("lucky",  "幸运222")
      )
);

运行结果:

[User{name=‘strive’, description=‘努力’}, User{name=‘fighter’, description=‘奋斗’}, User{name=‘lucky’, description=‘幸运’}, User{name=‘lucky’, description=‘幸运222’}]

2.1 forEach

forEach方法用于迭代遍历每个数据

//迭代遍历输出
stringList.forEach(str ->{
   
    System.out.print(str + " ");
});

运行结果:

a b c a d e a


2.2 filter

使用 filter 按照设置的条件过滤元素,得到满足条件的元素。

//获取stringList中非空字符串的集合
List<String> collect = stringList.stream().filter(str -> !str.isEmpty())
        .collect(Collectors.toList());
System.out.println("非空字符串集合:" + collect);

运行结果:

非空字符串集合:[a, b, c, a, d, e, a]

count() 方法,用来统计数量

long emptyStrNum = stringList.stream().filter(str -> str.isEmpty()).count();
System.out.println("空字符串数量 = " + emptyStrNum);

运行结果:

空字符串数量 = 3


2.3 distinct

distinct() 方法用于去重

//获取stringList中非空字符串的集合 去重后 转化为list集合
List<String> collect3 = stringList.stream().filter(str -> !str.isEmpty()).distinct()
        .collect(Collectors.toList());
System.out.println("去重后字符串集合:" + collect3);


//提取出userList对象中的属性name并去重
List<String> nameList = userList.stream().map(User::getName).distinct().collect(Collectors.toList());
System.out.println("nameList = " + nameList);

运行结果:

去重后字符串集合:[a, b, c, d, e]

nameList = [strive, fighter, lucky]


2.4 Collectors - (Collector工具库)

Collectors 类中实现了很多的规约操作(可用于返回列表或字符串)

最常用的是将流转换为 集合或聚合元素对象

2.4.1 Collectors.toList()方法将Stream转化为List对象
//查找非空、去重后通过 Collectors.toList() 转化为List列表
List<String> strList = stringList.stream().filter(str -> !str.isEmpty())
  .distinct().collect(Collectors.toList());
System.out.println("strList = " + strList);

运行结果:

strList = [a, b, c, d, e]

2.4.2 Collectors.toSet()方法将Stream转化为Set对象
//通过 Collectors.toSet() 方法转化为set列表
//set集合,不去重也输出相同的结果(set中不会有重复的元素)
Set<String> strSet = stringList.stream().filter(str -> !str.isEmpty())
  .collect(Collectors.toSet());
System.out.println("strSet = " + strList);

运行结果:

strSet = [a, b, c, d, e]

2.4.3 Collectors.toMap()方法将Stream转化为Map对象
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper,BinaryOperator mergeFunction,Supplier mapSupplier)

参数1: keyMapper 用来生成key值的。

参数2: valueMapper 用来生成value值的。

参数3: mergeFunction 用在key值冲突的情况下使用(可省略),如果新元素产生的key在Map中已经出现过了,第三个参数就会定义解决的办法。

参数4:mapSupplier 默认返回的map类型为hashMap,可以按自己的需要自己返回不同的map实现。( 可省略

主要举个3个参数的案例来说明:

Collectors.toMap(keyMapper, valueMapper, mergeFunction)

该toMap()方法有三个参数.

比如 collect Collectors.toMap(User::getName, i -> i, (v1, v2) -> v1)

第一个参数 User::getName 表示选择User对象的的getName方法获取的值作为map的key值;

第二个参数 i -> i 表示选择将原来的对象作为map的value值(这里的i只是对遍历对象取的别名)

第三个参数 (v1, v2) -> v1,当出现key值相同时(也就是如果v1与v2的key值相同),选择前面的 也就是v1 作为那个key所对应的值,**就是出现相同key时,谁覆盖谁的问题 **

案例:

创建一个Javabean对象User

@Data
class User{
   
    String name;
    String description;
}

A. 使用Collectors.toMap(keyMapper, valueMapper)两个参数来将List转化为Map

//初始化一个User的List列表
List<User> list = new ArrayList<>(
        Arrays.asList(
                new User("strive", "努力"),
                new User("fighter", "奋斗"),
                new User("lucky",  "幸运"),
                
        )
);
//Collectors.toMap()方法,将List转化为Map集合
//Map<String, String> map = list.stream().collect(Collectors.toMap(item -> item.getName(), item -> item.getDescription()));
//简写为下面这种方式
Map<String, User> map = list.stream().collect(Collectors.toMap(User::getName, i -> i));
System.out.println(map);

运行结果:

{lucky=User{name=‘lucky’, description=‘幸运’}, strive=User{name=‘strive’, description=‘努力’}, fighter=User{name=‘fighter’, description=‘奋斗’}}

B. 如果出现key相同,但是没有设置第三个参数对其进行处理,就会报错 IllegalStateException

 		//初始化一个User的List列表,里面有两个lucky
        List<User> list = new ArrayList<>(
                Arrays.asList(
                        new User("strive", "努力"),
                        new User("fighter", "奋斗"),
                        new User("lucky",  "幸运"),
                        new User("lucky",  "幸运222")
                )
        );	
        Map<String, User> map = list.stream().collect(Collectors.toMap(User::getName, i -> i));
        System.out.println(map);

运行结果:

报错:java.lang.IllegalStateException: Duplicate key User{name=‘lucky’, description=‘幸运’}

因为按照User的name转换有两个相同的key值lucky,没有选择处理方式报错:IllegalStateException

C. 设置第三个参数,解决出现相同key时,谁覆盖谁的问题

        List<User> list = new ArrayList<>(
                Arrays.asList(
                        new User("strive", "努力"),
                        new User("fighter", "奋斗"),
                        new User("lucky",  "幸运"),
                        new User("lucky",  "幸运222")
                )
        );
		//设置当key相同的时候,保留前面的 【如果为 (v1,v2)->v2 就是用新的覆盖旧的】
        Map<String, User> map = list.stream().collect(Collectors.toMap(User::getName, i -> i, (v1,v2) -> v1));
        System.out.println(map);

运行结果:

{lucky=User{name=‘lucky’, description=‘幸运’}, strive=User{name=‘strive’, description=‘努力’}, fighter=User{name=‘fighter’, description=‘奋斗’}}

2.4.4 统计

count获取集合数量

List<String> stringList = Arrays.asList("a","b", "", "", "c", "", "a", "d","e","a");
//获取集合StringList的元素数量
//Collectors.counting()
stringList.stream().collect(Collectors.counting());
//简写为count()
stringList.stream().count();
//等价于集合的size方法
(long) stringList.size();

运行结果:

三种结果最终的效果相同

10

10

10


求平均值:averagingInt、averagingLong、averagingDouble
求最大/最小值:maxBy、minBy
统计求和:summingInt、summingLong、summingDouble
统计所有(包括计数、求和、最小、最大、平均):summarizingInt、summarizingLong、summarizingDouble

 		List<Integer> intList = Arrays.asList(2, 3, 10, 6, 8, 5, 2, 9);

        // maxBy 求最大值  --  等价于max
        Optional<Integer> maxBy = intList.stream().collect(Collectors.maxBy(Integer::compareTo));
        // minBy 求最大值  --  等价于min
        Optional<Integer> minBy = intList.stream().collect(Collectors.minBy(Integer::compareTo));
        // averagingInt 求平均值
        Double averagingInt = intList.stream().collect(Collectors.averagingInt(i -> i));
        // 求和summingInt(需要求和的参数) -- 等价于 mapToInt
        Integer summingInt = intList.stream().collect(Collectors.summingInt(i -> i));
        //summarizingInt 统计数目、求和、最小值、平均值、最大值
        IntSummaryStatistics summarizingInt = intList.stream().collect(Collectors.summarizingInt(i -> i));

        System.out.println("maxBy = " + maxBy);
        System.out.println("minBy = " + minBy);
        System.out.println("averagingInt = " + averagingInt);
        System.out.println("summingInt = " + summingInt);
        System.out.println("summarizingInt = " + summarizingInt);

运行结果:

maxBy = Optional[10]
minBy = Optional[2]
averagingInt = 5.625
summingInt = 45
summarizingInt = IntSummaryStatistics{count=8, sum=45, min=2, average=5.625000, max=10}


2.4.5 分组

partitioningBy(分区):按照条件分为两个Map<Boolean, List>,一个是满足条件的Map和一个不满足条件的Map。
groupingBy(分组):类似于分区,但是是将集合按照条件分为多个Map,可以对进行分组之后的结果再分组

List<Integer> intList = Arrays.asList(2, 3, 10, 6, 8, 5, 2, 9);
        //按 >5 分为两个区间
        Map<Boolean, List<Integer>> partitioningBy = intList.stream().collect(Collectors.partitioningBy(i -> i > 5));
        //按 >5 分为两组
        Map<Boolean, List<Integer>> groupingBy = intList.stream().collect(Collectors.groupingBy(i -> i > 5));
        //先按 >5 分为两组,然后再在前面分组满足条件的基础上对(满足条件的集合)再对 >8 进行分组
        Map<Boolean, Map<Boolean, List<Integer>>> groupingBy2 = intList.stream().collect(Collectors.groupingBy(i -> i > 5, Collectors.groupingBy(i -> i > 8)));
        System.out.println("分区partitioningBy = " + partitioningBy);
        System.out.println("分组groupingBy = " + groupingBy);
        System.out.println("两次分组groupingBy2 = " + groupingBy2);

运行结果:

分区partitioningBy = {false=[2, 3, 5, 2], true=[10, 6, 8, 9]}
分组groupingBy = {false=[2, 3, 5, 2], true=[10, 6, 8, 9]}
两次分组groupingBy2 = {false={false=[2, 3, 5, 2]}, true={false=[6, 8], true=[10, 9]}}


2.4.6 joining(连接)

joining(条件):可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个新的字符串。

        List<String> stringList2 = Arrays.asList("a","b", " ", " ", "c", " ", "a", "d","e","a");
        String joining = stringList2.stream().collect(Collectors.joining("-"));
        System.out.println("joining = " + joining);

运行结果:

joining = a-b- - -c- -a-d-e-a


2.4.7 reducing (规约)

reducing(U identity, Function<? super T, ? extends U> mapper, BinaryOperator<U> op) 

reducing:Collectors类提供的reducing方法,类似于stream本身的reduce方法,但是增加了对自定义归约的支持。
参数 BinaryOperator<T>: 这是一个函数式接口,是给两个相同类型的量,返回一个跟这两个量相同类型的一个结果,伪表达式为 (T,T) -> T。默认给了两个实现 maxByminBy ,根据比较器来比较大小并分别返回最大值或者最小值。当然你也可以灵活定制。然后 reducing 就很好理解了,元素两两之间进行比较根据策略淘汰一个,随着轮次的进行元素个数就是 reduce 的。

        List<Integer> intList2 = Arrays.asList(1, 2, 3);
        Integer reducing = intList2.stream().collect(Collectors.reducing(100, i -> i, (v1, v2) -> (v1 + v2 - 1)));
        System.out.println("reducing = " + reducing);

运行结果:

reducing = 103

Stream提供的reduce方法也有类似的作用

        // stream的reduce  --  T reduce(T identity, BinaryOperator<T> accumulator);
        //参1:(identity):求出结果之后再加该值
        Optional<Integer> reduce = Optional.ofNullable(intList2.stream().reduce(100, Integer::sum));
        System.out.println("reduce = " + reduce.get());

运行结果:

reduce = 106

Optional<Integer> reduce = Optional.ofNullable(intList2.stream().reduce(100, Integer::sum, (v1, v2) -> (v1 + v2 - 1)));
System.out.println("reduce = " + reduce.get());

2.5 map、flatMap

map方法用于 映射每个元素到对应的结果,该函数会被应用到每个元素上,并将其映射成一个新的元素。

flatMap方法用于 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。

//使用 map 输出stringList中元素对应的两倍并去重
List<String> doubleStr = stringList.stream().map(str -> str += str).distinct()
        .collect(Collectors.toList());
System.out.print(doubleStr + " ");

运行结果:

[aa, bb, , cc, dd, ee]

//通过map获取userList列表中的User对象的name属性组成一个list列表
List<String> nameList = list.stream().map(user -> user.getName()).distinct()
        .collect(Collectors.toList());
System.out.println(nameList);

运行结果:

[strive, fighter, lucky]


//flagMap转换为流后再转化为list列表输出
List<String> s = stringList.stream().flatMap(s -> {
   
    //将每个元素按照分隔符,转换成一个stream
    String[] split = s.split(",");
    return Arrays.stream(split);
}).collect(Collectors.toList());
System.out.println(s);

运行结果:

[a, b, , , c, , a, d, e, a]


2.6 peek

peek 方法(消费),类似于map,能得到流中的每一个元素。但map接收的是一个Function表达式,有返回值;而peek接收的是Consumer表达式,是没有返回值的。

//peek消费,去重输出
stringList.stream().peek(i -> i += i).distinct().forEach(i-> System.out.print(i + " "));

输出结果:

a b c d e


2.7 limit

limit(n) 方法用于获取指定数量 (n个) 的流

Random random = new Random();
// 通过 limit 输出3个随机数
random.ints().limit(3).forEach(r ->{
   
    System.out.print(r + " ");
});

运行结果:

1677393552 -608502510 2060923188

// 三个参数:random.ints(生成数量,最小值,最大值)
//生成10个1-100之间的随机数,再通过limit取出5个输出
random.ints(10,1,100).limit(5).forEach(r ->{
   
    System.out.print(r + " ");
});

运行结果:

7 85 73 60 49


2.8 skip

skip(n) 方法用于跳过 (n个) 元素,配合 limit(n) 可实现分页

		List<Integer> numbers = Arrays.asList(0, 9 ,6 , 5, 6, 3, 2, 1, 12, 5, 8, 3, 9, 3);
		//过滤得到集合中>1的元素,然后去重,跳过前2个,然后取出5个
        List<Integer> collect = numbers.stream().filter(i -> i > 1)
                .distinct()
                .skip(2)
                .limit(5).collect(Collectors.toList());
        System.out.println(collect);

运行结果:

[5, 3, 2, 12, 8]


2.9 sorted

sorted() 方法用于对流进行排序,这是自然排序,流中元素需实现Comparable接口。

sorted(Comparator com) : 带参数是定制排序,使用自定义的Comparator排序器进行排序

Random random = new Random();
//两个参数:random.ints(最小值,最大值)
//随机生成0-1000的随机数,通过limit取8个,然后排序输出
random.ints(0,1000)
        .limit(8).sorted().forEach(i ->{
   
    System.out.print(i + " ");
});

运行结果:

86 192 232 552 560 776 928 929


2.10 统计结果收集器

比如:getCount、getMax、getMin、getSum、getAverage等等用于统计结果的收集器,主要用于int、double、long等基本类型上

//定义一个int类型的集合
List<Integer> numbers = Arrays.asList(6, 2, 1, 2, 5, 8, 3, 9);
//通过mapToInt转化
IntSummaryStatistics intStatus = numbers.stream().mapToInt((i) -> i).summaryStatistics();

System.out.println("列表中元素数量:" + intStatus.getCount());
System.out.println("列表中最大数 : " + intStatus.getMax());
System.out.println("列表中最小数 : " + intStatus.getMin());
System.out.println("所有数之和 : " + intStatus.getSum());
System.out.println("平均值 : " + intStatus.getAverage());

运行结果:

列表中元素数量:8
列表中最大数 : 9
列表中最小数 : 1
所有数之和 : 36
平均值 : 4.5


2.11 流的终止操作

方法名称 描述
count 返回流中元素的总个数
max 返回流中元素最大值
min 返回流中元素最小值
findFirst 返回流中第一个元素
findAny 返回流中第一个元素(随机)
allMatch 接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回true,否则返回false
noneMatch 接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回true,否则返回false
anyMatch 接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回false
        List<Integer> intList = Arrays.asList(2, 3, 10, 6, 8, 5, 2, 9);

        long count = intList.stream().count();// 等价于intList.size()
        Integer max = intList.stream().max(Integer::compareTo).get();
        Integer min = intList.stream().min(Integer::compareTo).get();
        Integer findFirst = intList.stream().findFirst().get();
        Integer findAny = intList.stream().findAny().get();
        boolean allMatch = intList.stream().allMatch(i -> i > 1);
        boolean noneMatch = intList.stream().noneMatch(i -> i > 1);
        boolean anyMatch = intList.stream().anyMatch(i -> i > 5);

运行结果:

count = 8
max = 10
min = 2
findFirst = 2
findAny = 2
allMatch = true
noneMatch = false
anyMatch = true

图片来自:https://ifeve.com/stream/


3. 创建流的两种方式

创建流的方式 描述
stream 为集合创建串行流
parallelStream 为集合创建并行流

parallelStream里面的执行是异步的,并且使用的线程池是 ForkJoinPool.common,可以通过设置 -Djava.util.concurrent.ForkJoinPool.common.parallelism = N 来调整线程池的大小。可能提高你的多线程任务的速度。

        List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5, 6);
        System.out.print("stream串行:");
        intList.stream().forEach(System.out::print);
        System.out.print("\nparallelStream并行:");
        intList.parallelStream().forEach(System.out::print);

三次运行结果:

第一次运行:

stream串行:123456
parallelStream并行:456321

第二次运行:

stream串行:123456
parallelStream并行:451623

第三次运行:

stream串行:123456
parallelStream并行:465321

可以发现,Stream每次运行都是相同的结果,且是顺序输出的,但是parallelStream每次运行结果可能不同,顺序也是错乱的。

打印执行线程信息,查看是否是并行的

        List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5, 6);
        intList.parallelStream().forEach(i -> System.out.println("线程 = " + Thread.currentThread().getName() +" : 输出 = " + i));

运行结果:

线程 = main : 输出 = 4
线程 = ForkJoinPool.commonPool-worker-2 : 输出 = 1
线程 = ForkJoinPool.commonPool-worker-2 : 输出 = 3
线程 = ForkJoinPool.commonPool-worker-2 : 输出 = 5
线程 = main : 输出 = 6
线程 = ForkJoinPool.commonPool-worker-9 : 输出 = 2

所以 parallelStream 是利用多线程并行执行的,通过 parallelStream 可以很大程度简化我们使用并发操作。

使用 parallelStream 是平行处理的,所以顺序每次都不一定一致,如果想要顺序是按照原来Stream的数据一样顺序输出,可以通过 forEachOrdered 方法实现。

        List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5, 6);
        System.out.println("parallelStream并行使用forEachOrdered顺序输出:");
        intList.parallelStream().forEachOrdered(System.out::print);
        System.out.println();
        intList.parallelStream().forEachOrdered(i ->   System.out.println("线程 = " + Thread.currentThread().getName() +" : 输出 = " + i));

运行结果:

parallelStream并行使用forEachOrdered顺序输出:123456
线程 = ForkJoinPool.commonPool-worker-6 : 输出 = 1
线程 = ForkJoinPool.commonPool-worker-6 : 输出 = 2
线程 = ForkJoinPool.commonPool-worker-6 : 输出 = 3
线程 = ForkJoinPool.commonPool-worker-6 : 输出 = 4
线程 = ForkJoinPool.commonPool-worker-6 : 输出 = 5
线程 = ForkJoinPool.commonPool-worker-6 : 输出 = 6

所以调用 forEachOrdered 方法顺序执行的话,就不是多线程并行处理了,是一个线程进行处理。


转载:https://blog.csdn.net/qq_40542534/article/details/112908006
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场