《后端程序猿 · Java8 Stream》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻一周,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,如需交流,欢迎留言评论。👍

文章目录

    • 写在前面的话
    • 技能概述
      • 技能简介
      • 基础操作
    • 常用积累
      • 实用积累
      • List 转 Map
    • 中间操作
      • 过滤专栏(filter)
      • 排序专栏(sorted)
      • 映射专栏(map、peek)
    • 终止操作
      • 遍历输出(forEach)
      • 匹配聚合(find、match)
      • 收集操作(collect)
      • 规约操作(reduce)

写在前面的话

Java8 两大最为重要的改变就是 Lambda 表达式和 Steam API,其中,Stream API 提供了一种高效且易用的处理集合数据的方式,类似于使用SQL语法。
关于用不用 Stream 的写法,每个人有每个人的看法, Stream 带来方便的同时也带来一些其他问题,那个人觉得还是利大于弊,简洁的完成功能开发,这点就够了。


技能概述

技能简介

一段描述:
1、Java 8 API 添加了一个新的抽象称为流 Stream,可以让你以一种声明的方式处理数据,java.util.Stream 表示能应用在一组元素上一次执行的操作序列;
2、Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回 Stream 本身,这样你就可以将多个操作依次串起来;
3、Stream 的创建需要指定一个数据源,比如 java.util.Collection 的子类,List 或者 Set,而 Map 不支持,Stream 的操作可以串行执行或者并行执行;
4、Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API 可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等,元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

补充介绍:
和以前的 Collection 操作不同, Stream 操作还有两个基础的特征:
1、管道处理:中间操作都会返回流对象本身,这样多个操作可以串联成一个管道, 如同流式风格。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路(short-circuiting)。
2、内部迭代:以前对集合遍历都是通过 Iterator 或者 For-Each 的方式,显式的在集合外部进行迭代,这叫做外部迭代。 Stream 提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

特征介绍:
1、中间操作惰性执行:一个流后面可以跟随0到多个中间操作,主要目的是打开流,并没有真正的去计算,而是做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历,并没有消耗资源。含有多个中间操作的话,这里的时间复杂度并不是n个for循环,转换操作都是 lazy 的,多个转换操作只会在 Terminal 操作的时候融合起来,一次循环完成。可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在Terminal操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。
2、流的末端操作只能有一次: 当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。之后如果想要操作就必须新打开流。

注意事项:
1、惰性求值(如果没有终结操作,只包含中间操作是不会得到执行的)
2、流是一次性的(一旦一个流对象经过一个终结操作后。这个流就不能再被使用)
3、不会影响原数据(我们在流中可以多数据做很多处理。但是正常情况下是不会影响原来集合中的元素的,这往往也是我们期望的)

基础操作

Stream 的操作分为三个步骤:创建 Stream - 中间操作 - 终止操作

如何创建流
方式一,Java 8 扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream。

两种流的简单区分:stream 是顺序流,由主线程按顺序对流执行操作,而 parallelStream 是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。如果流中的数据量足够大,并行流可以加快处速度。

List<String> list = Arrays.asList("a", "b", "c");
// 创建一个串行流
Stream<String> stream = list.stream();
// 创建一个并行流
Stream<String> parallelStream = list.parallelStream();

方式二,对于数组,使用 java.util.Arrays.stream(T[]array) 方法用数组创建流。

int[] array={1,3,5,6,8}; 
IntStream stream = Arrays.stream(array);

方式三,也可以使用 Stream 的静态方法,of()、iterate()、generate()

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);

//创建无限流,用的较少,限制返回前四个,输出0 3 6 9
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 3).limit(4);
stream2.forEach(System.out::println);

//创建无限流,用的较少
Stream<Double> stream3 = Stream.generate(Math::random).limit(3);
stream3.forEach(System.out::println);

PS:用的最多就是针对集合进行操作,也就是方式一的 Collection.stream()。

如何操作流
流的操作类型分2种:中间操作与聚合操作(也叫终止操作)。
1、中间操作:对容器的处理过程,如:排序(sorted…),筛选(filter,limit,distinct…),映射(map,flatMap…)等,中间操作返回值都是 Stream,因此可以不断继续往下用。
2、终止操作:之前的中间操作只是对流中数据的处理,最终我们还是要将它们整合输出为一个结果,如果没有终止操作则不会真正执行。

//基础Demo
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(
    string -> !string.isEmpty()).collect(Collectors.toList());

//例子1
Stream<String> stream = Stream.of("I", "got", "you", "too");

//例子2
String [] strArray = new String[] {"a", "b", "c"};
stream = Arrays.stream(strArray);

//例子3
List<String> list = Arrays.asList(strArray);
stream = list.stream();

常用积累

实用积累

//Java8 List 根据对象属性去重
List<String> names = new ArrayList<>();//用来临时存储person的id
List<Person> personList = persons.stream().filter(// 过滤去重
        v -> {
            boolean flag = !names.contains(v.getName());
            names.add(v.getName());
            return flag;
        }
).collect(Collectors.toList());

//List与String的相互转换(Java8)
//字符串转List<String>
String str = "测试1, 测试2, 测试3, 测试4";
//此处为了将字符串中的空格去除做了一下操作
List<String> list= Arrays.asList(str.split(",")).stream()
    .map(s -> (s.trim())).collect(Collectors.toList());
//List<String>转字符串(以逗号隔开)
System.out.println(String.join(",", list));

//合并两个流
Stream<Integer> firstStream = Stream.of(1, 2, 3);
Stream<Integer> secondStream = Stream.of(4, 5, 6);
Stream<Integer> resultingStream = Stream.concat(firstStream, secondStream);
System.out.println( resultingStream.collect(Collectors.toList()) );

List 转 Map

**描述:**无论后端还是前端,都有这个场景,后端List(或前端Array),通常不能直接用来渲染使用,根据某个属性,转换为后端Map<String,List>(或前端JSON对象),方便后续使用。

警示:需要转换思维,组装临时Map可以处理很多问题,例如前端渲染问题,多重循环问题等等。
注意:不管是Java和JavaScript都有如此操作,不用 Stream 的传统写法也要掌握,更加灵活!

// 范例1,Key 和 Value 都是 String
Map<String, String> storeNameMap = ckStoreDicts.stream()
    .collect(Collectors.toMap(CkStoreDict::getStoreCode, CkStoreDict::getStoreName));

// 范例2,Value 是对象本身,两种写法
Map<String, CkDeptDict> map = ckDeptDicts.stream()
    .collect(Collectors.toMap(CkDeptDict::getDeptCode, Function.identity()));
Map<String, CkDeptDict> map2 = ckDeptDicts.stream()
    .collect(Collectors.toMap(CkDeptDict::getDeptCode, a -> a));

补充:key重复问题处理
上述逻辑适合于Key是唯一主键的场景,若 Key 可能重复,则需要额外处理,解决方式就在 Java8 提供的Collectors.toMap() 方法中,其第三个参数就是当出现 duplicate key的时候的处理方案。

// 方案一: 出现重复时,取前面value的值,或者取后面放入的value值,则覆盖先前的value值

// 1.1 取后面的值,舍弃前面的值
Map<Long, String> map = userList.stream()
            .collect(Collectors.toMap(User::getId, User::getUsername, (v1, v2) -> v2));

// 1.2 取前面的值,舍弃后面的值
Map<Long, String> map = userList.stream()
            .collect(Collectors.toMap(User::getId, User::getUsername, (v1, v2) -> v1));

//方案二: Map的value可以储存一个list,把重复key的值放入list,再存到value中
userList.stream().collect(Collectors.toMap(User::getId,
                e -> Arrays.asList(e.getUsername()),
                (List<String> oldList, List<String> newList) -> {
                    oldList.addAll(newList);
                    return oldList;
                }));

补充:stream toMap 之 value为空的问题
在使用 Collectors.toMap 时需要记住几点:
1、key 不能有重复,否则会报错,因为Map的key不能重复;
2、value不能为空,否则报空指针;
若确实存在value为空的场景,可以使用如下方案替换:

Map<String,String> tokenToIdMap = allEntities.stream().collect(HashMap::new, (m,v)->
        m.put(v.getDeviceToken(), v.getDfDeviceId()), HashMap::putAll);

List 分组转 Map(即转换为<String, List>)

List<Matchs> matchsList = new ArrayList();
Map<String,List<Matchs>> MatchsListMap=matchsList.stream().collect(Collectors.groupingBy(Matchs::getMatchDate)); 
Map<String, List<Map<String, Object>>> matchsListMap2 = dataList2.stream()
                .collect(Collectors.groupingBy(x -> x.get("name") + "" ));

中间操作

过滤专栏(filter)

filter 基础用法
filter 方法用于通过设置的条件(Predicate 接口)过滤出元素,即从集合中选出想要的内容。
描述:从 List 里面筛选出符合要求的内容,常用于 Dao 层获取列表后,需要二次过滤的场景。

// 范例1,Dao操作后,根据入参过滤
List<CkBuyOrderDetail> collect = ckBuyOrderDetailService.findOrderList(ckBuyOrderDetail);
if (ValidUtil.isNotEmptyAndNull(ckBuyOrderDetail.getBakInfo())) {
    collect = collect.stream()
      .filter(u -> ckBuyOrderDetail.getBakInfo().equals(u.getBakInfo()))
      .collect(Collectors.toList());
}

其他过滤类型操作:
1、limit(long maxSize):获得指定数量的流,返回前n个。
2、skip(long n):去除前 n 个元素,配合 limit 可实现分页,在 limit(n) 前面时,先去除前 m 个元素再返回剩余元素的前 n 个元素,反之,limit(n) 用在 skip(m) 前面时,先返回前 n 个元素再在剩余的 n 个元素中去除 m 个元素。
3、distinct():通过hashCode和equals去除重复元素,对象去重必须指定equals方法。

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

// 过滤操作
Stream<Integer> stream = Stream.of(6, 4, 6, 7, 3, 9, 8, 10, 12, 14, 14);
Stream<Integer> newStream = stream.filter(s -> s > 5) //6 6 7 9 8 10 12 14 14
        .distinct() //6 7 9 8 10 12 14
        .skip(2) //9 8 10 12 14
        .limit(2) //9 8
        .forEach(System.out::println);

//limit 方法用于获取指定数量的流。 
//截取10条
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
String[] arr1 = { "a", "b", "c", "d" };
String[] arr2 = { "d", "e", "f", "g" };
Stream<String> stream1 = Stream.of(arr1);
Stream<String> stream2 = Stream.of(arr2);

// concat:合并两个流 distinct:去重
List<String> newList = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList());

// limit:限制从流中获得前n个数据
List<Integer> collect = Stream.iterate(1, x -> x + 2).limit(10).collect(Collectors.toList());

// skip:跳过前n个数据
List<Integer> collect2 = Stream.iterate(1, x -> x + 2).skip(1).limit(5).collect(Collectors.toList());

System.out.println("流合并:" + newList);
System.out.println("limit:" + collect);
System.out.println("skip:" + collect2);

扩展:根据某字段去重

PS:distinct 用的场景较少,下面这种根据某个属性去重更常见。

dataList = dataList.stream()
                .collect(Collectors.collectingAndThen(
                        Collectors.toCollection(
                                () -> new TreeSet<>(Comparator.comparing(LwUserInfoTemp :: getBonuses))), ArrayList::new)
                );

排序专栏(sorted)

如果流中的元素的类实现了 Comparable 接口,即有自己的排序规则,那么可以直接调用 sorted() 方法对元素进行排序,反之, 需要调用 sorted((T, T) -> int) 实现 Comparator 接口。
排序是一个中间操作,返回的是排序好后的 Stream,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据stringCollection是不会被修改的。

//使用 sorted 方法对输出的 10 个随机数进行排序
Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);

//按照用户年龄进行排序(升序/降序)并且取top3
userList.stream()
    .sorted(Comparator.comparing(User::getAge).reversed())
    .limit(3)
    .collect(Collectors.toList());


//由大到小排序
//前面-后面,大于0则换位置,说明是升序,前面大放在后面
// Stream<T> sorted(Comparator<? super T> comparator);
list = list.stream()
  .sorted((p1, p2) -> p1.getAge() - p2.getAge())
   //可进一步简化为.sorted(Comparator.comparingInt(Person::getAge))
  .collect(toList());
List<LwUserInfoTemp> dataList = new ArrayList<>();
dataList.add(LwUserInfoTemp.builder().bonuses(1).userName("张三1").lastLoginTime(DateUtils.parseDate("2022-02-01")).build());
dataList.add(LwUserInfoTemp.builder().bonuses(2).userName("张三2").lastLoginTime(DateUtils.parseDate("2023-02-01")).build());
dataList.add(LwUserInfoTemp.builder().bonuses(4).userName("张三3").lastLoginTime(DateUtils.parseDate("2021-02-01")).build());
dataList.add(LwUserInfoTemp.builder().bonuses(3).userName("张三4").lastLoginTime(DateUtils.parseDate("2024-02-01")).build());

//PS:根据整型升序排序,并且需要赋值出来
dataList = dataList.stream().sorted(Comparator.comparing(LwUserInfoTemp::getBonuses)).collect(Collectors.toList());

//PS:降序排序,后面-前面大于0则调换位置,说明是降序
dataList = dataList.stream().sorted((o1, o2) -> o2.getBonuses() - o1.getBonuses()).collect(Collectors.toList());

//PS:时间降序
//PS:时间类型字段排序不能直接用getTime
dataList = dataList.stream().sorted((o1, o2) -> {
    long a = o2.getLastLoginTime().getTime();
    long b = o1.getLastLoginTime().getTime();
    if (a > b) {
        return 1;
    } else if (b > a) {
        return -1;
    } else {
        return 0;
    }
}).collect(Collectors.toList());

dataList = dataList.stream().sorted((o1, o2) -> (o2.getLastLoginTime().compareTo(o1.getLastLoginTime()))).collect(Collectors.toList());

dataList.forEach(System.out::println);
// 按工资升序排序(自然排序)
List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName)
        .collect(Collectors.toList());
// 按工资倒序排序
List<String> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed())
        .map(Person::getName).collect(Collectors.toList());
// 先按工资再按年龄升序排序
List<String> newList3 = personList.stream()
        .sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).map(Person::getName)
        .collect(Collectors.toList());
// 先按工资再按年龄自定义排序(降序)
List<String> newList4 = personList.stream().sorted((p1, p2) -> {
    if (p1.getSalary() == p2.getSalary()) {
        return p2.getAge() - p1.getAge();
    } else {
        return p2.getSalary() - p1.getSalary();
    }
}).map(Person::getName).collect(Collectors.toList());

System.out.println("按工资升序排序:" + newList);
System.out.println("按工资降序排序:" + newList2);
System.out.println("先按工资再按年龄升序排序:" + newList3);
System.out.println("先按工资再按年龄自定义降序排序:" + newList4);

null 值处理
当对象出现空值,会报错,如下操作:

@Test
public void testListSort() {
    List<Book> bookList = new ArrayList<>();
    bookList.add(new Book(null, "水浒传"));
    bookList.add(new Book(9, "钢铁怎样炼成的"));
    bookList.add(new Book(null, "百年孤独"));
    bookList.add(new Book(3, "三国演义"));
    bookList.add(new Book(18, "史记"));
    System.out.println("bookList = " + bookList);

    /*
      整理升序,price为null的排在最后
     */
    List<Book> bookList1 = bookList.stream().sorted(
            Comparator.comparing(Book::getPrice, Comparator.nullsLast(Comparator.naturalOrder()))).collect(Collectors.toList());
    System.out.println("整理升序,price为null的排在最后:");
    System.out.println("bookList1 = " + bookList1);

    /*
      整体升序,price为null的排在最前
     */
    List<Book> bookList2 = bookList.stream().sorted(
            Comparator.comparing(Book::getPrice, Comparator.nullsFirst(Comparator.naturalOrder()))).collect(Collectors.toList());
    System.out.println("整体升序,price为null的排在最前:");
    System.out.println("bookList2 = " + bookList2);

    /*
      整体降序,price为null的排在最后
     */
    List<Book> bookList3 = bookList.stream().sorted(
            Comparator.comparing(Book::getPrice, Comparator.nullsLast(Comparator.reverseOrder()))).collect(Collectors.toList());
    System.out.println("整体降序,price为null的排在最后:");
    System.out.println("bookList3 = " + bookList3);

    /*
      整体降序,price为null的排在最前
     */
    List<Book> bookList4 = bookList.stream().sorted(
            Comparator.comparing(Book::getPrice, Comparator.nullsFirst(Comparator.reverseOrder()))).collect(Collectors.toList());
    System.out.println("整体降序,price为null的排在最前");
    System.out.println("bookList4 = " + bookList4);
}

映射专栏(map、peek)

就是对每个元素进行加工处理!

1、map 方法用于映射每个元素到对应的结果,即处理集合中的某个对象重包装后返回。
2、map(T -> R) 将流中的每一个元素 T 映射为 R(类似类型转换),其实就是 Function 接口,map 返回的 Stream 类型是根据你map传递进去的函数的返回值决定的。
3、映射操作,就像一个管道,可以将流中的元素通过一个函数进行映射,返回一个新的元素。这样遍历映射,最终返回一个新的容器,注意:这里返回的新容器数据类型可以不与原容器类型相同。
4、flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。


// <R> Stream<R> map(Function<? super T, ? extends R> mapper);
//基础操作,转换大写
List<String> list = Arrays.asList("how", "are", "you", "how", "old", "are", "you", "?");
list.stream().map(item -> item.toUpperCase()).forEach(System.out::println);

//基础操作,整数数组每个元素+3
List<Integer> list = Arrays.asList(1, 17, 27, 7);
List<Integer> collect = list.stream().map(x -> x + 3).collect(Collectors.toList());

//对象操作,属性修改
List<Person> collect = personList.stream().map(x -> {
    x.setAge(x.getSalary()+2000);
    return x;
}).collect(Collectors.toList());

// 将多个集合中的元素合并成一个集合
List<Integer> mergeList = Stream.of(a, b).flatMap(list -> list.stream()).collect(Collectors.toList());
// [1, 2, 3, 4, 5, 6]
System.out.println(mergeList);

/**
 * 将两个字符数组合并成一个新的字符数组
 * @return [z,  h,  a,  n,  g, s,  a,  n]
 */
String[] arr = {"z, h, a, n, g", "s, a, n"};
List<String> list = Arrays.asList(arr);
List<String> collect = list.stream().flatMap(x -> {
    String[] array = x.split(",");
    Stream<String> stream = Arrays.stream(array);
    return stream;
}).collect(Collectors.toList());
System.out.println(collect);

// 获取对应的平方数
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());

//将List<VO> 截取其中一个属性转为 List<String>
List<Page> dataList = new ArrayList<>();
dataList.stream().map(Page::getPageSize).forEach(System.out::println);;

//将List<Map> 截取其中一个属性转为 List<String>
List<Map<String, String>> dataList = new ArrayList<>();
dataList.stream().map(v -> v.get("a")).forEach(System.out::println);

List<String> list = Arrays.asList("a,b,c", "1,2,3");
 
//将每个元素转成一个新的且不带逗号的元素
Stream<String> s1 = list.stream().map(s -> s.replaceAll(",", ""));
s1.forEach(System.out::println); // abc  123
 
Stream<String> s3 = list.stream().flatMap(s -> {
    //将每个元素转换成一个stream
    String[] split = s.split(",");
    Stream<String> s2 = Arrays.stream(split);
    return s2;
});
s3.forEach(System.out::println); // a b c 1 2 3

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

List<Student> studentList = Arrays.asList(s1, s2);
studentList.stream()
        .peek(o -> o.setAge(100))
        .forEach(System.out::println);

终止操作

遍历输出(forEach)

forEach 是为 Lambda 而设计的,保持了最紧凑的风格。

foreach 是一个终端操作,参数也是一个函数,它会迭代中间操作完成后的每一个数据,这里它将每个不为空的元素打印出来,通常用于遍历输出。

//::是方法引用的写法,等同于.forEach((s) -> System.out.println(s))
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);

//准备数据
List<LwUserInfoTemp> dataList = new ArrayList<>();
dataList.add(LwUserInfoTemp.builder().bonuses(1).userName("张三1").lastLoginTime(DateUtils.parseDate("2022-02-01")).build());
dataList.add(LwUserInfoTemp.builder().bonuses(2).userName("张三2").lastLoginTime(DateUtils.parseDate("2023-02-01")).build());
dataList.add(LwUserInfoTemp.builder().bonuses(4).userName("张三3").lastLoginTime(DateUtils.parseDate("2021-02-01")).build());
dataList.add(LwUserInfoTemp.builder().bonuses(3).userName("张三4").lastLoginTime(DateUtils.parseDate("2024-02-01")).build());

//便捷输出
dataList.forEach(System.out::println);

补充知识 - foreach 用法:
foreach其实就是for的加强版,其语法如下:
for(元素类型 元素变量x:遍历对象(数组或集合)){
引用元素变量x的语句;
}
List 的 forEach方法,内部也是 foreach用法:

numbers.forEach((e) -> {
    e = e * 10;
    System.out.print(e + " ");
});

补充知识 - toString 用法:
当使用 System.out.println(ts) 输出一个实体类对象时,实际是调用该对象的 toString 方法。
为方便输出结果的查看,可以重写该类的的 toString 方法。

@Override
public String toString() {
    return String.format("姓名%s,积分%s,时间%s", userName, bonuses, DateUtils.toLongStringFormat(lastLoginTime));
}

匹配聚合(find、match)

Stream 也是支持类似集合的遍历和匹配元素的,只是 Stream中的元素是以 Optional类型存在的。

匹配的具体方法如下:
allMatch:当流中每个元素都符合该断言时才返回true,否则返回false
noneMatch:当流中每个元素都不符合该断言时才返回true,否则返回false
anyMatch:只要流中有一个元素满足该断言则返回true,否则返回false
findFirst:返回第一个元素,常与orElse一起用,例如:findFirst().orElse(null)
findAny:返回流中的任意元素

注意一下关于返回值 Optional 的处理方式。

List<Integer> list = Arrays.asList(7, 6, 9, 3, 8, 2, 1);

// 遍历输出符合条件的元素
list.stream().filter(x -> x > 6).forEach(System.out::println);

// 匹配第一个
Optional<Integer> findFirst = list.stream().filter(x -> x > 6).findFirst();
findFirst.ifPresent(integer -> System.out.println("匹配第一个值:" + integer));

// 匹配任意(适用于并行流)
Optional<Integer> findAny = list.parallelStream().filter(x -> x > 6).findAny();
findAny.ifPresent(integer -> System.out.println("匹配任意一个值:" + integer));

// 是否包含符合特定条件的元素
boolean anyMatch = list.stream().anyMatch(x -> x < 6);
System.out.println("是否存在大于6的值:" + anyMatch);

支持如下聚合操作:
count:返回流中元素的总个数
max:返回流中元素最大值
min:返回流中元素最小值

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
 
boolean allMatch = list.stream().allMatch(e -> e > 10); //false
boolean noneMatch = list.stream().noneMatch(e -> e > 10); //true
boolean anyMatch = list.stream().anyMatch(e -> e > 4);  //true

// 接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true
// boolean anyMatch(Predicate<? super T> predicate);
// parallelStream可以并行计算,速度比stream更快
List<String> list = Arrays.asList("you", "give", "me", "stop");
boolean result = list.parallelStream().anyMatch(item -> item.equals("me"));
System.out.println(result);
 
// findFirst:返回第一个元素
Integer findFirst = list.stream().findFirst().get(); //1

// findAny:返回流中的任意元素
Integer findAny = list.stream().findAny().get(); //1

long count = list.stream().count(); //5

// Optional<T> min(Comparator<? super T> comparator);
Integer max = list.stream().max(Integer::compareTo).get(); //5
Integer min = list.stream().min(Integer::compareTo).get(); //1

收集操作(collect)

collect 是一个非常常用的末端操作,常常使用collect将流转换成List,Map或Set,对应的方法是
Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。
Collectors 可用于返回列表或字符串:

//最基础的转List
List<String> list = stream.collect(Collectors.toList());

// Stream -> Object[]
Object[] objects = list.stream().toArray();
//积分过滤后转Map,这里第二个参数写Function.identity()也可以
Map<String, LwUserInfoTemp> collect = dataList.stream().filter(x -> x.getBonuses() > 3).collect(Collectors.toMap(LwUserInfoTemp::getUserName, y -> y));
System.out.println(collect);

//转Map
Map<String, Integer> maps =
aList.stream().collect(Collectors.toMap(Function.identity(), String::length));
{中山北路=4, 大学=2, 仙林大学城=5, 仙林校区=4, 南京=2}

//转List
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);

//转字符串
//joining可将元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。
String mergedString = strings.stream().filter(string -> !string.isEmpty())
    .collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);

Collectors提供了一系列用于数据统计的静态方法:
计数: count
平均值: averagingInt、 averagingLong、 averagingDouble
最值: maxBy、 minBy
求和: summingInt、 summingLong、 summingDouble
统计以上所有: summarizingInt、 summarizingLong、 summarizingDouble

//统计员工人数
Long count = personList.stream().collect(Collectors.counting());
//求平均工资
Double average = personList.stream().collect(Collectors.averagingDouble(Person::getSalary));
//求最高工资
Optional<Integer> max = personList.stream().map(Person::getSalary).collect(Collectors.maxBy(Integer::compare));
//求工资之和
Integer sum = personList.stream().collect(Collectors.summingInt(Person::getSalary));
//一次性统计所有信息
DoubleSummaryStatistics collect = personList.stream().collect(Collectors.summarizingDouble(Person::getSalary));
System.out.println("统计员工人数:"+count);
System.out.println("求平均工资:"+average);
System.out.println("求最高工资:"+max);
System.out.println("求工资之和:"+sum);
System.out.println("一次性统计所有信息:"+collect);

分组(partitioningBy/groupingBy)
分区:将stream按条件分为两个 Map,比如员工按薪资是否高于8000分为两部分。
分组:将集合分为多个Map,比如员工按性别分组。有单级分组和多级分组。

PS:得到的结果是Map,就是以前的List转Map<String, List>结构

// 将员工按薪资是否高于8000分组
Map<Boolean, List<Person>> part = personList.stream().collect(Collectors.partitioningBy(x -> x.getSalary() > 8000));

// 将员工按性别分组
Map<String,List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getSex));

//按积分分组
Map<Integer, List<LwUserInfoTemp>> map = dataList.stream().collect(Collectors.groupingBy(LwUserInfoTemp::getBonuses));
System.out.println(map);

//统计每组数量
Map<String, Long> collect = students.stream().collect(Collectors.groupingBy(Student::getClassNumber, Collectors.counting()));
System.out.println(JSON.toJSONString(collect));
//{"700":2,"701":3,"703":1}  

// 将员工先按性别分组,再按地区分组
Map<String, Map<String, List<Person>>> group2 = personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getArea)));
System.out.println("员工按薪资是否大于8000分组情况:" + part);
System.out.println("员工按性别分组情况:" + group);
System.out.println("员工按性别、地区:" + group2);

参考:Java8 stream流之分组 groupingBy 的使用

规约操作(reduce)

归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。

reduce 就是减少的意思,它会将集合中的所有值根据规则计算,最后只返回一个结果。
它有三个变种,输入参数分别是一个参数、二个参数以及三个参数。

//拼接字符串
Stream<String> stream = Stream.of("you", "give", "me", "stop");
Optional<String> optional = stream.reduce((before, after) -> before + "," + after);
optional.ifPresent(System.out::println);    // you,give,me,stop

//求和计算
// 66.66
List<BigDecimal> list = Arrays.asList(
        new BigDecimal("11.11"),
        new BigDecimal("22.22"),
        new BigDecimal("33.33")
);
BigDecimal sum = list.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
System.out.println(sum);

//整型的操作
List<Integer> list = Arrays.asList(1, 2, 3, 4);
//求和
Optional<Integer> reduce = list.stream().reduce((x,y) -> x+ y);
System.out.println("求和:"+reduce);
//求积
Optional<Integer> reduce2 = list.stream().reduce((x,y) -> x * y);
System.out.println("求积:"+reduce2);
//求最大值
Optional<Integer> reduce3 = list.stream().reduce((x,y) -> x>y?x:y);
System.out.println("求最大值:"+reduce3);

//对象类型操作
Optional<Integer> reduce = personList.stream().map(Person :: getSalary).reduce(Integer::sum);
Optional<Integer> reduce2 = personList.stream().map(Person :: getSalary).reduce(Integer::max);
System.out.println("工资之和:"+reduce);
System.out.println("最高工资:"+reduce2);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/775864.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

掌握React与TypeScript:从零开始绘制中国地图

最近我需要使用reactts绘制一个界面&#xff0c;里面需要以中国地图的形式展示区块链从2019-2024年这五年的备案以及注销情况&#xff0c;所以研究了一下这方面的工作&#xff0c;初步有了一些成果&#xff0c;所以现在做一些分享&#xff0c;希望对大家有帮助&#xff01; 在这…

64、哥伦比亚大学:CU-Net-目前脑肿瘤分割的最先进模型

本文已被接受发表在2024年IEEE MLISE会议上&#xff08;c&#xff09;2024 IEEE。准确地将脑肿瘤从MRI扫描中分割出来对于制定有效的治疗方案和改善患者预后至关重要。本研究引入了一种新的哥伦比亚大学网络&#xff08;CU-Net&#xff09;架构实现&#xff0c;用于使用BraTS 2…

哪个品牌的加密软件稳定方便使用?

一、什么是企业加密软件&#xff1f; 企业加密软件是一种用于保护企业内部数据安全的工具。在数字化时代&#xff0c;随着数据量的爆炸式增长&#xff0c;信息安全和隐私保护变得愈发重要。企业加密软件作为保障数据安全的关键工具&#xff0c;受到越来越多用户的青睐。 企业…

【专业指南】移动硬盘坏道下的数据恢复之道

移动硬盘坏道揭秘&#xff1a;数据安全的隐形挑战 在数据日益成为核心资产的今天&#xff0c;移动硬盘作为便携存储的代名词&#xff0c;承载着无数用户的重要信息。然而&#xff0c;随着使用时间的增长和不当操作的影响&#xff0c;移动硬盘可能会遭遇“坏道”这一棘手问题。…

谷粒商城学习-11-docker安装redis

文章目录 一&#xff0c;拉取Redis镜像1&#xff0c;搜索Redis的Docker镜像2&#xff0c;拉取Redis镜像3&#xff0c;查看已经拉取的镜像 二&#xff0c;创建、启动Redis容器1&#xff0c;创建redis配置文件2&#xff0c;创建及运行Redis容器3&#xff0c;使用docker ps查看运行…

GSR解读 | 7月7日起,所有新车出海欧洲将强制配备这些ADAS功能!

今年以来&#xff0c;“出海”“卷到海外去”成为大大小小车企活动中的高频词。在国内卷无可卷的主机厂们逐渐将战火烧到海外&#xff0c;而欧洲则成为大部分车厂的出海第一站&#xff0c;如蔚来、极氪、小鹏都在欧洲建立了本地团队或子公司。 中国车企出海欧洲在高歌猛进的同…

RAM和ROM的区别

RAM和ROM的区别 RAM和ROM都是用来存东西的&#xff0c;比如我们熟悉的CPU缓存、电脑和手机的内存就是属于RAM&#xff0c;而固态硬盘、U盘&#xff0c;还有我们买手机时候说的32G、64G的存储空间&#xff0c;就属于ROM。RAM和ROM的区别&#xff0c;简单说就是RAM在断电之后&am…

前端面试题12(js异步方法)

在JavaScript中&#xff0c;异步编程是处理延迟操作&#xff08;如网络请求、定时器等&#xff09;的关键方式&#xff0c;它允许代码在等待某些操作完成时继续执行&#xff0c;提高了应用的响应性和用户体验。 回调函数&#xff08;Callback&#xff09; 回调是最原始的异步处…

spark shuffle写操作——BypassMergeSortShuffleWriter

创建分区文件writer 每一个分区都生成一个临时文件&#xff0c;创建DiskBlockObjectWriter对象&#xff0c;放入partitionWriters 分区writer写入消息 遍历所有消息&#xff0c;每一条消息都使用分区器选择对应分区的writer然后写入 生成分区文件 将分区writer的数据flu…

用html+css设计一个列表清单小卡片

目录 简介: 效果图: 源代码: 可能的问题: 简介: 这个HTML代码片段是一个简单的列表清单设计。它包含一个卡片元素(class为"card"),内部包含一个无序列表(ul),列表项(li)前面有一个特殊的符号(△)。整个卡片元素设计成300px宽,150px高,具有圆角边…

【字符串】【滑动窗口+位运算+双指针】1、无重复字符的最长子串+2、尽可能使字符串相等+3、最长优雅子数组+4、移动零+5、反转字符串

2道简单3道中等 1、无重复字符的最长子串&#xff08;难度&#xff1a;中等&#xff09; 该题对应力扣网址 超时代码 老实说&#xff0c;在我写博客的时候&#xff0c;也不知道为啥超时了&#xff0c;因为我看和我AC的代码时间也差不了多少吧&#xff08;如果有大佬知道&…

误删分区后的数据拯救:双管齐下恢复策略

在数字化时代&#xff0c;数据的价值日益凸显&#xff0c;而误删分区作为常见的数据安全威胁之一&#xff0c;常常让用户措手不及。本文将深入探讨误删分区的现象&#xff0c;并为您揭示两种高效的数据恢复方案&#xff0c;旨在帮助您在最短时间内找回失去的数据&#xff0c;同…

1117 数字之王

solution 判断现有数字是否全为个位数 全为个位数&#xff0c;找出出现次数最多的数字&#xff0c;并首行输出最多出现次数&#xff0c;第二行输出所有出现该次数的数值不全为个位数 若当前位数值为0&#xff0c;无需处理若当前位数值非0&#xff0c;则每位立方相乘&#xff0…

Linux搭建hive手册

一、将hive安装包上传到NameNode节点并解压 1、删除安装MySQL时的.rpm文件 cd /opt/install_packages/ rm -rf *.rpm 2、将安装包拖进/install_packages目录 3、解压安装包 tar -zxvf apache-hive-3.1.2-bin.tar.gz -C /opt/softs/ 4、修改包名 cd /opt/softs mv apache-…

虚拟机下基于海思移植QT(一)——虚拟机下安装QT

0.参考资料 1.海思Hi3516DV300 移植Qt 运行并在HDMI显示器上显示 2.搭建海思3559A-Qt4.8.7Openssl开发环境 1.报错解决 通过下面命令查询 strings /lib/x86_64-linux-gnu/libc.so.6 | grep GLIBC_通过命令行没有解决&#xff1a; sudo apt install libc6-dev libc6参考解决…

【国产开源可视化引擎Meta2d.js】锚点

国产开源 乐吾乐潜心研发&#xff0c;自主可控&#xff0c;持续迭代优化 Github&#xff1a;GitHub - le5le-com/meta2d.js: The meta2d.js is real-time data exchange and interactive web 2D engine. Developers are able to build Web SCADA, IoT, Digital twins and so …

【C语言题目】34.猜凶手

文章目录 作业标题作业内容2.解题思路3.具体代码 作业标题 猜凶手 作业内容 日本某地发生了一件谋杀案&#xff0c;警察通过排查确定杀人凶手必为4个嫌疑犯的一个。 以下为4个嫌疑犯的供词: A说&#xff1a;不是我。 B说&#xff1a;是C。 C说&#xff1a;是D。 D说&#xff…

软件是什么?一个软件到底是哪些部分组成的-软件到底有哪些分支呢?

https://doc.youyacao.com/117/2163 软件是什么&#xff1f;一个软件到底是哪些部分组成的-软件到底有哪些分支呢&#xff1f; 何为软件 软件定义 的本质是通过软件编程实现硬件资源的虚拟化、灵活、多样和定制化功能&#xff0c;以最大化系统运行效率和能量效率。它基于硬…

SSM中小学生信息管理系统-计算机毕业设计源码02677

摘要 随着社会的发展和教育的进步&#xff0c;中小学生信息管理系统成为学校管理的重要工具。本论文旨在基于SSM框架&#xff0c;采用Java编程语言和MySQL数据库&#xff0c;设计和开发一套高效、可靠的中小学生信息管理系统。中小学生信息管理系统以学生为中心&#xff0c;通过…

H2 Database Console未授权访问漏洞封堵

背景 H2 Database Console未授权访问&#xff0c;默认情况下自动创建不存在的数据库&#xff0c;从而导致未授权访问。各种未授权访问的教程&#xff0c;但是它怎么封堵呢&#xff1f; -ifExists 很简单&#xff0c;启动参数添加 -ifExists &#xff0c;它的含义&#xff1a…