Java学习十一—Java8特性之Stream流

一、Java8新特性简介

2014年3月18日,JDK8发布,提供了Lambda表达式支持、内置Nashorn JavaScript引擎支持、新的时间日期API、彻底移除HotSpot永久代。

111111

Java 8引入了许多令人兴奋的新特性,其中最引人注目的是Lambda表达式和Stream API。以下是Java 8的一些主要新特性介绍:

  1. Lambda表达式:Lambda表达式是Java 8中最重要的特性之一。它允许您以更简洁的方式编写匿名函数,并将其作为参数传递给方法。Lambda表达式使代码更易读和更易维护。
  2. Stream API:Stream API为集合框架新增了对函数式编程的支持。通过Stream API,您可以以声明性的方式处理集合数据,例如过滤、映射、排序等操作。这样可以更容易地编写并发、并行化的代码。
  3. 接口的默认方法:Java 8允许在接口中定义默认方法。这使得接口可以包含具体的方法实现,而不仅仅是抽象方法。这样可以在不破坏现有实现的情况下向接口添加新的方法。
  4. 方法引用:方法引用是一种更简洁的Lambda表达式的替代方式。它允许您直接引用现有方法或构造函数,而不必重新编写Lambda表达式。
  5. 新的时间日期API:Java 8引入了全新的时间日期API,即java.time包。这个API解决了旧的Date和Calendar类存在的许多问题,并提供了更好的日期和时间处理功能。
  6. CompletableFuture:CompletableFuture是一种新的异步编程工具,用于简化异步任务的处理。它提供了更灵活的方式来处理异步操作的结果和异常。
  7. Nashorn JavaScript引擎:Java 8引入了Nashorn JavaScript引擎,用于在Java应用程序中执行JavaScript代码。这使得Java与JavaScript之间的互操作更加方便。
  8. Optional 类:Optional<T>​ 类用于表示可能为空的值,避免了显式使用 null​,减少了空指针异常的风险。

二、Stream流

2.1关于Stream流

2.1.1简介

Java Stream API 是 Java 8 引入的一项强大功能,主要用于处理集合数据。它提供了一种声明性的方法来处理数据,使代码更加简洁和易读。

222

2.1.2特点

Stream API 的一些关键特点:

  1. 声明式编程:使用 Stream API 时,你只需要指定“做什么”,而不需要关心“怎么做”。这使得代码更加简洁、易读。
  2. 惰性求值:Stream API 的操作是惰性的,这意味着在调用终端操作之前,中间操作不会执行。这有助于提高性能,因为它允许 JVM 优化操作的执行。
  3. 不可变性:Stream 本身是不可变的,一旦创建,就不能修改。这有助于避免并发修改异常。
  4. 并行处理:Stream API 支持并行处理,可以利用多核处理器提高性能。只需将 Stream 转换为并行 Stream,就可以并行执行操作。
  5. 丰富的操作:Stream API 提供了丰富的中间操作(如 filter​、map​、reduce​)和终端操作(如 forEach​、collect​、min​、max​)。
  6. 类型安全:Stream API 与 Java 的泛型系统紧密集成,确保了类型安全。
  7. 无状态与有状态操作:中间操作分为无状态操作(如 filter​)和有状态操作(如 distinct​、sorted​)。无状态操作的结果只依赖于当前元素,而有状态操作可能需要考虑多个元素。
  8. 短路操作:某些终端操作(如 anyMatch​、allMatch​、noneMatch​)可以在满足特定条件时提前终止,这称为短路操作。
  9. Optional 支持:某些操作(如 findFirst​、findAny​)返回 Optional​ 类型,以避免空指针异常。
  10. 集合操作:Stream API 可以轻松地与集合框架集成,如使用 Collection.stream()​ 方法将集合转换为 Stream。

2.1.3使用流程

Java Stream 的一般流程:创建流 → 应用中间操作(可以有多个中间操作) → 应用终端操作。流的操作可以链式调用,形成流畅的操作序列,提高了代码的简洁性和可读性。同时,Java Stream 也利用了并行处理来提升性能,特别是对于大数据集合的处理。

2.2创建stream流

  • 从集合创建:

    List<String> list = Arrays.asList("a", "b", "c");
    Stream<String> streamFromList = list.stream();
    
  • 从数组创建:

    String[] array = {"a", "b", "c"};
    Stream<String> streamFromArray = Arrays.stream(array);
    
  • 使用 Stream.of() 方法:

    Stream<String> stream = Stream.of("a", "b", "c");
    
  • 从文件创建(使用 NIO):

    try (Stream<String> lines = Files.lines(Paths.get("file.txt"))) {
        // 使用 lines 进行操作
    } catch (IOException e) {
        e.printStackTrace();
    }
    

2.2.1list集合创建

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

2.2.2map集合创建

keyset

那么这里的keyset当然就是把双列集合里的所有的键数据的set集合放在流里面了

//    双列集合
        HashMap<String,Integer> map = new HashMap<>();
        map.put("zhangsan",23);
        map.put("lisi",24);
        map.put("wangwu",25);
        map.keySet().stream().forEach(s -> System.out.println(s));

entryset
//    双列集合
        HashMap<String,Integer> map = new HashMap<>();
        map.put("zhangsan",23);
        map.put("lisi",24);
        map.put("wangwu",25);
//      map.keySet().stream().forEach(s -> System.out.println(s));
        map.entrySet().stream().forEach(s-> System.out.println(s));

2.2.3数组创建流

可以使用数组的帮助类Arrays中的静态方法stream生成流

//        数组
        int  [] arr ={1,2,3,4,5};
        Arrays.stream(arr).forEach(s-> System.out.println(s));

2.2.4同种数据类型的多个数据

可以通过Stream里面的of方法,来获取到一个stream流
Stream.of(T…Values)生成流,可以看到这个of方法里是一个可变参数,所以不管传多少参数都是可以的

	//同种数据类型的多个数据
    Stream.of(1,2,3,4,5).forEach(s-> System.out.println(s));

2.2.5并行流-Parallel-Streams

前面章节我们说过,stream​ 流是支持顺序并行的。顺序流操作是单线程操作,而并行流是通过多线程来处理的,能够充分利用物理机 多核 CPU 的优势,同时处理速度更快。

测试:

首先,我们创建一个包含 1000000 UUID list 集合。

int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
    UUID uuid = UUID.randomUUID();
    values.add(uuid.toString());
}

分别通过顺序流和并行流,对这个 list 进行排序,测算耗时:

顺序流排序
// 纳秒
long t0 = System.nanoTime();

long count = values.stream().sorted().count();
System.out.println(count);

long t1 = System.nanoTime();

// 纳秒转微秒
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("顺序流排序耗时: %d ms", millis));

// 顺序流排序耗时: 899 ms

并行流排序
// 纳秒
long t0 = System.nanoTime();

long count = values.parallelStream().sorted().count();
System.out.println(count);

long t1 = System.nanoTime();

// 纳秒转微秒
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("并行流排序耗时: %d ms", millis));

// 并行流排序耗时: 472 ms

正如你所见,同样的逻辑处理,通过并行流,我们的性能提升了近 50% 。完成这一切,我们需要做的仅仅是将 stream​ 改成了 parallelStream​。

区别

stream()​ 和 Parallel-Streams​ 是 Java 8 引入的流式操作 API 中的两个重要概念。它们都属于 Java 8 新增的 Stream API,用于处理集合数据的函数式编程方式。

stream()​ 方法返回一个顺序流(sequential stream),它将集合转换为一个按顺序处理的流。顺序流适用于串行处理数据的场景,即每个元素依次经过一系列的中间操作和终端操作。

Parallel-Streams​ 则返回一个并行流(parallel stream),它将集合转换为一个可以并行处理的流。并行流适用于需要并行处理大量数据的场景,可以充分利用多核处理器的优势,提高处理速度。

使用场景区别如下:

  1. stream()​:适用于处理规模较小的数据集或要求顺序处理的场景。顺序流的处理过程是串行的,适合对数据进行逐个处理,保持处理顺序的情况。
  2. Parallel-Streams​:适用于处理大规模数据集或可以并行处理的场景。并行流的处理过程可以同时使用多个线程进行处理,能够加速处理速度。但需要注意,并行流的处理可能会引入线程安全问题,需要谨慎处理共享状态。

需要注意的是,并行流的性能提升并不是适用于所有场景的,有时候并行处理的开销可能会超过性能收益。因此,在选择使用 Parallel-Streams​ 时,需要根据具体情况进行评估和测试。

综上所述,stream()​ 适用于顺序处理小规模数据集的场景,而 Parallel-Streams​ 适用于并行处理大规模数据集的场景,可以根据实际需求选择合适的流类型。

2.3中间操作

Java Stream API 的中间操作是一系列操作,它们创建了一个新的 Stream,并且可以在这个新的 Stream 上继续添加操作。中间操作不会立即执行,它们是惰性的,只有在终端操作被调用时才会实际执行。以下是一些常见的中间操作方法及其示例:

2.3.1filter 过滤

筛选出符合条件的元素。

示例
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evenNumbers = numbers.stream()
                                    .filter(n -> n % 2 == 0)
                                    .collect(Collectors.toList());
// 输出: [2, 4]

过滤元素以某一字母开头

首先,我们创建一个 List​ 集合:

List<String> stringCollection = new ArrayList<>();
stringCollection.add("ddd2");
stringCollection.add("aaa2");
stringCollection.add("bbb1");
stringCollection.add("aaa1");
stringCollection.add("bbb3");
stringCollection.add("ccc");
stringCollection.add("bbb2");
stringCollection.add("ddd1");

Filter​ 的入参是一个 Predicate​, 上面已经说到,Predicate​ 是一个断言的中间操作,它能够帮我们筛选出我们需要的集合元素。它的返参同样 是一个 Stream​ 流,我们可以通过 foreach​ 终端操作,来打印被筛选的元素:

stringCollection
    .stream()
    .filter((s) -> s.startsWith("a"))
    .forEach(System.out::println);

// "aaa2", "aaa1"

注意:foreach​ 是一个终端操作,它的返参是 void​, 我们无法对其再次进行流操作。

image-20230629184113992

2.3.2map 转换

将流中的元素通过指定的映射函数转换成另一个值。

示例
List<String> words = Arrays.asList("hello", "world");
List<Integer> wordLengths = words.stream()
                                 .map(String::length)
                                 .collect(Collectors.toList());
// 输出: [5, 5]

对集合中的每个元素应用给定的函数。

中间操作 Map​ 能够帮助我们将 List​ 中的每一个元素做功能处理。例如下面的示例,通过 map​ 我们将每一个 string​ 转成大写:

stringCollection
    .stream()
    .map(String::toUpperCase)
    .sorted((a, b) -> b.compareTo(a))
    .forEach(System.out::println);

// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"

2.3.3flatMap 转换

将 Stream 中的每个元素转换成另一个 Stream,然后将这些 Stream 连接起来。

将流中的每个元素映射成一个流,然后合并成一个新的流。

示例
List<List<Integer>> numbers = Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3, 4));
List<Integer> flattenedList = numbers.stream()
                                     .flatMap(List::stream)
                                     .collect(Collectors.toList());
// 输出: [1, 2, 3, 4]

2.3.4distinct(去重)

去除流中重复的元素。依赖hashcode和equals方法,所以如果是自定义类的话,就必须在自定义类里面重写hashcode和equals方法

示例
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 4, 4, 4);
List<Integer> distinctNumbers = numbers.stream()
                                       .distinct()
                                       .collect(Collectors.toList());
// 输出: [1, 2, 3, 4]

ArrayList<String> list1 = new ArrayList<>();
        list.add("张三丰");
        list.add("张无忌");
        list.add("张翠山");
        list.add("王二麻子"); 
        list.add("王二麻子"); 
    list.stream().distinct().forEach(s -> System.out.println(s));
 /*结果:张三丰
      	张无忌
        张翠山
        王二麻子*/

2.3.5sorted 排序

对流中的元素进行排序,默认是自然顺序,也可以传入自定义的比较器。

Sorted​ 同样是一个中间操作,它的返参是一个 Stream​ 流。另外,我们可以传入一个 Comparator​ 用来自定义排序,如果不传,则使用默认的排序规则。

示例
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5);
List<Integer> sortedNumbers = numbers.stream()
                                     .sorted()
                                     .collect(Collectors.toList());
// 输出: [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]

按照对象某个字段排序
        //国家按sortOrder排序
        List<AgentCountryDO> sortedCountryDOS = countryDOS.stream()
                .sorted(Comparator.comparingLong(AgentCountryDO::getSortOrder))
                .collect(Collectors.toList());

按照倒序排序

        List<String> yearList = this.lambdaQuery().select(BudgetInfoDO::getYear)
                .list()
                .stream()
                .map(BudgetInfoDO::getYear)
                .distinct()
                .sorted(Comparator.reverseOrder())
                .collect(Collectors.toList());

2.3.6peek方法

peek用于处理集合中元素(对象)的某个属性的值,但不改变元素(对象)的类型(区别于map操作)

示例
package listDemo;
 
import org.apache.commons.lang3.StringUtils;
 
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
 
public class StreamPeekDemo {
 
    public static void main(String[] args) {
 
        List<Person> personList = new ArrayList<>();
        personList.add(new Person("xiaozhang", 20));
        personList.add(new Person("xiaowang", 21));
 
        List<Person> peekedList = personList.stream().peek(e -> e.setCity("beijing")).collect(Collectors.toList());
        System.out.println(StringUtils.join(peekedList, "-"));
 
    }
}
package listDemo;
 
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
 
@Data
@RequiredArgsConstructor
public class Person {
 
    @NonNull
    private String name;
    @NonNull
    private int age;
    private String city;
 
}

运行结果

Person(name=xiaozhang, age=20, city=beijing)-Person(name=xiaowang, age=21, city=beijing)

2.3.7limit(取用前几个)

Stream limit (long maxSize): 截取指定的参数个数的数据

示例
ArrayList<String> list = new ArrayList<>();
        list.add("孙悟空");
        list.add("唐三藏");
        list.add("猪八戒");
        list.add("沙悟净");
	//简化前代码:
 	Stream<String> stream = list.stream();
	stream.forEach(s -> System.out.println(s));

	//简化后代码:
	list.stream().limit(2).forEach(s -> System.out.println(s));
		// 结果:孙悟空
		//		唐三藏
	//注解:本方法截取就是 只保留前面的指定元素

2.3.8skip(跳过前几个)

跳过指定参数个数的数据 ,就是里面这个方法里面传几,那么就会跳过前几个数据

示例
ArrayList<String> list = new ArrayList<>();
        list.add("孙悟空");
        list.add("唐三藏");
        list.add("猪八戒");
        list.add("沙悟净");
    //Stream<T> skip (long n):  跳过指定参数个数的数据
    list.stream().skip(2).forEach(s -> System.out.println(s));
        //结果:猪八戒
        //     沙悟净
    //注解: 与截取相反 本方法 是跳过前面的指定元素

image

2.4终端操作

2.4.1collect(收集操作)

将 Stream 中的元素收集到集合或其他容器中。

示例
List<String> list = Arrays.asList("a", "b", "c");
List<String> collected = list.stream().collect(Collectors.toList());

注意事项:在stream流中无法直接修改集合,数组等数据源中的数据,你能修改的仅仅是流上的数据

2.4.2Collectors​​工具类

Collectors​ 是 Java 8 引入的 java.util.stream​ 包中的一个实用类,提供了多种静态方法来生成常见的收集器实例。收集器用于将流的元素处理为汇总结果,如将元素收集到一个集合、聚合元素、分组、分区等。下面是 Collectors​ 类的一些常见方法及其简介:

toList()
  • 将流中的元素收集到一个 List​ 中。
List<String> list = stream.collect(Collectors.toList());

将流中的元素收集到一个List集合中。

	List<Integer> collect = list.stream().filter(number -> number % 2 == 0).collect(Collectors.toList());
	System.out.println(collect);
		//filter 负责过滤数据的
		//collect 负责收集数据的, 获取流中剩余的数据,但是他不会负责创建容器,也不负责把数据添加到容器中。

toSet()
  • 将流中的元素收集到一个 Set​ 中,去除重复元素。
Set<String> set = stream.collect(Collectors.toSet());
 Set<Integer> collect1 = list.stream().filter(number -> number % 2 == 0).collect(Collectors.toSet());
 System.out.println(collect1);
	//filter 负责过滤数据的
	//collect 负责收集数据的, 获取流中剩余的数据,但是他不会负责创建容器,也不负责把数据添加到容器中。

toMap()
  • 将流中的元素收集到一个 Map​ 中,需要指定键和值的映射函数,可以通过合并函数处理重复键。
Map<Integer, String> map = stream.collect(Collectors.toMap(
    String::length,  // 键映射器
    Function.identity(),  // 值映射器
    (existing, replacement) -> existing));  // 合并函数(处理重复键)

示例

用到了函数Function.identity()

List<GoodsSpuDO> spuDOList = this.lambdaQuery()
                .in(GoodsSpuDO::getSpuId, spuIds)
                .eq(GoodsSpuDO::getLangCode,LangCodeEnum.DEFAULT.getCode())
                .eq(GoodsSpuDO::getEnableFlag, SystemConstants.ENABLE_FLAG_ON)
                .list();
        List<GoodsSpuResourcesDO> spuResourceList = resourcesService.lambdaQuery()
                .in(GoodsSpuResourcesDO::getSpuId, spuIds)
                .eq(GoodsSpuResourcesDO::getEnableFlag, SystemConstants.ENABLE_FLAG_ON)
                .eq(GoodsSpuResourcesDO::getDataGroup, GoodsSpuResourceGroupEnums.LIST_PIC.getDataGroupCode())
                .in(GoodsSpuResourcesDO::getLangCode,LangCodeEnum.DEFAULT.getCode(),LangCodeEnum.ALL.getCode())
                .list();
        Map<Long, GoodsSpuDO> spuMap = spuDOList.stream().collect(Collectors.toMap(GoodsSpuDO::getSpuId, Function.identity()));

image

这段代码使用Java 8中的Stream API和Collectors.toMap()方法将一个包含GoodsSpuDO对象的spuDOList列表转换为一个Map\long,,其中Long是GoodsSpuDO对象的spuId属性,GoodsSpuDO对象本身作为值

Function.identity()​​是一个函数引用,表示GoodsSpuDO​​对象本身作为值。

joining()
  • 将流中的元素连接成一个字符串,默认使用空字符串作为分隔符。可以指定分隔符、前缀和后缀。
String result = stream.collect(Collectors.joining(", ", "[", "]"));

将流中的元素拼接成一个字符串。

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class ListStreamJoiningExample {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("Java", "is", "a", "programming", "language");

        // 使用joining()方法连接字符串
        String result = words.stream()
                .collect(Collectors.joining(" "));

        // 输出结果
        System.out.println(result);
    }
}

在这个案例中,我们定义了一个包含多个字符串的List集合。然后,我们使用stream()方法将List转换为一个Stream对象。接着,我们调用collect(Collectors.joining(" "))方法将Stream中的所有字符串连接起来,每个字符串之间用空格分隔。最后,我们将连接后的字符串输出到控制台。

运行上述代码,输出结果如下:

Java is a programming language

可以看到,List集合中的所有字符串都被连接起来,并以空格分隔。

mapping()
  • 在收集之前应用一个额外的映射函数。
List<String> names = persons.stream()
    .collect(Collectors.mapping(Person::getName, Collectors.toList()));

mapping

Collectors.mapping​ 是一个用于收集流中元素的收集器,它可以将流中的元素进行映射操作,并将映射结果收集到指定的容器中。

Collectors.mapping​ 方法的作用是将流中的元素进行映射操作,将每个元素按照 mapper​ 函数进行转换,然后将转换后的结果传递给 downstream​ 收集器进行进一步的收集操作。

通常,Collectors.mapping​ 方法与 Collectors.toList()​、Collectors.toSet()​ 等收集器组合使用,用于收集映射结果到列表或集合中。

例如,以下代码示例将一个字符串列表中的每个字符串转换为大写,并将结果收集到一个新的列表中:

List<String> strings = Arrays.asList("apple", "banana", "orange");
List<String> uppercaseList = strings.stream()
        .collect(Collectors.mapping(String::toUpperCase, Collectors.toList()));
System.out.println(uppercaseList);

输出:

[APPLE, BANANA, ORANGE]

在这个示例中,mapping​ 方法的 mapper​ 是 String::toUpperCase​,即将字符串转换为大写。downstream​ 是 Collectors.toList()​,用于将映射结果收集到列表中。

注意,Collectors.mapping​ 方法是在 Java 8 中引入的,并且需要与 Java 8 或更高版本一起使用。

        Map<Long, List<String>> idSynonymMap = synonymDOS.stream().collect(Collectors.groupingBy(
                SelectFunctionPointSynonymDO::getSubjectTermId,
                Collectors.mapping(SelectFunctionPointSynonymDO::getSynonym, Collectors.toList())
        ));

groupingBy()
  • 根据分类函数对流中的元素进行分组,生成一个 Map​,键是分类标准,值是对应的元素列表。
Map<String, List<Person>> peopleByCity = people.stream()
    .collect(Collectors.groupingBy(Person::getCity));

Collectors.groupingBy根据一个或多个属性对集合中的项目进行分组

接下来这个示例,将会按年龄对所有人进行分组:

Map<Integer, List<Person>> personsByAge = persons
    .stream()
    .collect(Collectors.groupingBy(p -> p.age)); // 以年龄为 key,进行分组

personsByAge
    .forEach((age, p) -> System.out.format("age %s: %s\n", age, p));

// age 18: [Max]
// age 23: [Peter, Pamela]
// age 12: [David]

@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
public class OrderDetailDTO {

    /**
     * 订单明细id
     */
    private Long id;

    /**
     * 订单id
     */
    private Long orderId;

    /**
     * 客户id
     */
    private Long customerId;

    /**
     * 同spu表中的spu_id
     */
    private Long spuId;

    /**
     * 同sku表中的sku_id
     */
    private Long skuId;

image

根据OrderDetailDTO对象列表中的spuId属性进行分组,将具有相同spuId的OrderDetailDTO对象放入同一个列表中,并将结果存储在一个Map<Long, List>对象中。

        Map<Long, List<String>> idSynonymMap = synonymDOS.stream().collect(Collectors.groupingBy(
                SelectFunctionPointSynonymDO::getSubjectTermId,
                Collectors.mapping(SelectFunctionPointSynonymDO::getSynonym, Collectors.toList())
        ));

reducing()
  • 使用归约操作将流中的元素结合起来,类似于 reduce()​ 操作。
Optional<Integer> sum = stream.collect(Collectors.reducing(Integer::sum));

partitioningBy()
  • 根据谓词(条件)将流中的元素分成两组(true/false),生成一个 Map<Boolean, List<T>>​​。
Map<Boolean, List<Integer>> partitioned = numbers.stream()
    .collect(Collectors.partitioningBy(n -> n % 2 == 0));

counting()
  • 计算流中元素的数量,返回 Long​​ 类型的结果。
long count = stream.collect(Collectors.counting());

summarizingInt(), summarizingDouble(), summarizingLong()
  • 生成一个统计信息对象,包括最大值、最小值、平均值、总和等。
IntSummaryStatistics stats = stream.collect(Collectors.summarizingInt(Integer::intValue));

summingLong

  Map<String, Long> resultData = combinedBuyingOrderDOList.stream()
                .collect(Collectors.groupingBy(e -> e.getActivityId().toString() + e.getSkuId().toString(), Collectors.summingLong(CombinedBuyingOrderDO::getCommodityNum)));

这段代码使用了 Java 8 中的 Stream API 和 Collectors 工具类,对一个 CombinedBuyingOrderDO 对象列表进行了分组和求和计算。

具体来说,代码中的这个 CombinedBuyingOrderDO 类包含了一些属性,如 activityId、skuId 和 commodityNum 等。现在有一个 CombinedBuyingOrderDO 对象列表 combinedBuyingOrderDOList,需要对其中的数据进行统计计算。假设列表中有多个 CombinedBuyingOrderDO 对象,它们的 activityId 和 skuId 可能相同(即表示同一种商品),而 commodityNum 表示该商品的购买数量。

代码的作用是根据 activityId 和 skuId 将 CombinedBuyingOrderDO 对象进行分组,并对同一组中的 CombinedBuyingOrderDO 对象的 commodityNum 属性进行求和操作。最终得到一个 Map 对象 resultData,其中 key 是 activityId 和 skuId 组合后的字符串,value 是同一组中 CombinedBuyingOrderDO 对象的 commodityNum 属性的和。

具体来说,代码中的 Collectors.groupingBy() 方法将 CombinedBuyingOrderDO 对象列表按照 activityId 和 skuId 进行分组,生成一个以 “activityId + skuId” 为 key,以 CombinedBuyingOrderDO 对象的 List 为 value 的 Map 对象。

然后,在每个分组中使用 Collectors.summingLong() 方法对 CombinedBuyingOrderDO 对象的 commodityNum 属性求和。最终,得到的结果就是一个以 “activityId + skuId” 为 key,以该组 CombinedBuyingOrderDO 对象的 commodityNum 属性值之和为 value 的 Map 对象。

minBy和maxBy

Java 8 流的新类 java.util.stream.Collectors 实现了 java.util.stream.Collector 接口,同时又提供了大量的方法对流 ( stream ) 的元素执行 map and reduce 操作,或者统计操作。

Collectors中的maxBy & minBy这两个函数和lambda中的max&min作用相同

@Test
public void maxByAndMinByExample() {
    List<String> list = Arrays.asList("1", "2", "3", "4");
    Optional<String> max = list.stream().collect(Collectors.maxBy((s, v) -> s.compareTo(v)));
    Optional<String> min = list.stream().collect(Collectors.minBy((s, v) -> s.compareTo(v)));
    System.out.println(max.get());
    System.out.println(min.get());
}

结果

4
1

@Test
public void maxAndMinExample() {
    List<String> list = Arrays.asList("1", "2", "3", "4");
    Optional<String> max = list.stream().max((s, v) -> s.compareTo(v));
    Optional<String> min = list.stream().min((s, v) -> s.compareTo(v));
    System.out.println(max.get());
    System.out.println(min.get());
}

结果

4
1

注意:
经过对比发现,直接使用max|min代码会更简洁、易读

2.4.3 其它

toList()

.toList()​ 方法用于将流(Stream)中的元素收集到一个列表中。这个方法通常用于将流转换为一个标准的 Java 集合类,如 ArrayList​ 或 LinkedList​。

示例
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamToListExample {
    public static void main(String[] args) {
        // 创建一个字符串流
        Stream<String> stream = Stream.of("apple", "banana", "orange", "kiwi");

        // 将流中的元素收集到一个列表中
        List<String> list = stream.toList();

        // 打印列表中的元素
        System.out.println(list); // 输出: [apple, banana, orange, kiwi]
    }
}

.toList();与collect(Collectors.toList())的区别

stream.toList()​ 和 collect(Collectors.toList())​ 都可以将 Java 8 中的 Stream​ 转换为一个 List​ 集合,但它们有一些区别。

  1. 引入方式:

    • stream.toList()​ 是 Stream​ 接口的默认方法,可以直接在 Stream​ 对象上调用。
    • collect(Collectors.toList())​ 是使用 Collectors​ 工具类中的静态方法,需要通过 collect​ 方法结合具体的收集器来使用。
  2. 可变性:

    • stream.toList()​ 返回的是一个不可变的 List​。对返回的 List​ 进行增删操作会抛出 UnsupportedOperationException​ 异常。
    • collect(Collectors.toList())​ 返回的是一个可变的 ArrayList​。可以对返回的列表进行增删操作。
  3. 需要额外的类型转换:

    • stream.toList()​ 返回的是 List​ 类型,不需要进行类型转换。
    • collect(Collectors.toList())​ 返回的是 List​ 接口的实现类 ArrayList​,如果要使用 List​ 接口引用接收结果,需要进行类型转换。

综上所述,两种方式都可以将 Stream​ 转换为 List​,但是在可变性和类型转换方面有所差异。选择哪种方式取决于具体的需求和使用场景。

toArray() :将 Stream 转换为数组
List<String> list = Arrays.asList("a", "b", "c");
String[] array = list.stream().toArray(String[]::new);

forEach(逐一处理)

对 Stream 中的每个元素执行指定操作。

示例
List<String> list = Arrays.asList("a", "b", "c");
list.stream().forEach(System.out::println);
public static class Student{
        private String name;
        private String sex;
        private String age;

        public String getName() {
            return name;
        }

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

public static void main(String[] args) {
        Student student = new Student();
        student.setName("张三");
        student.setSex("女");
        student.setAge("18");
        Student student1 = new Student();
        student1.setName("王美美");
        student1.setSex("男");
        student1.setAge("57");
        List<Student> studentList = new ArrayList<>();
        studentList.add(student);
        studentList.add(student1);
        studentList.stream().forEach(stu->{
            System.out.println(stu.getName());
        });
    }


打印结果:张三
         王美美

image

集合foreach与stream foreach的区别

forEach​和Stream.forEach​在功能上非常相似,它们都用于遍历集合的元素并执行给定的操作。然而,它们在实现上存在一些细微的区别。

  1. 使用方式:

    • forEach​是Iterable​接口的默认方法,可以直接在集合上使用,如list.forEach(action)​。
    • Stream.forEach​是Stream​接口的方法,需要通过stream()​方法将集合转换为流,然后调用forEach(action)​,如list.stream().forEach(action)​。
  2. 可变性:

    • forEach​方法可以在遍历过程中修改原始集合的元素,因为它是直接作用于集合上的。
    • Stream.forEach​方法是一个终端操作,它对于流的每个元素执行给定的操作,但不直接修改原始集合中的元素。
  3. 并行处理:

    • Stream.forEach​方法支持并行处理,可以使用parallelStream()​方法将集合转换为并行流,从而实现并行执行给定的操作。
    • forEach​方法不直接支持并行处理,它只能按顺序逐个执行操作。

需要注意的是,尽管Stream.forEach​方法支持并行处理,但在某些情况下,并行化操作可能会引入线程安全或同步问题。因此,在并行处理时,请确保操作是线程安全的。

总之,forEach​方法是基于集合的操作,可以直接修改集合元素,而Stream.forEach​方法是基于流的操作,不会直接修改原始集合,并且支持并行处理。根据具体的需求和场景,选择适合的方法来遍历和处理集合。

count 计数

返回 Stream 中元素的个数。

count​ 是一个终端操作,它能够统计 stream​ 流中的元素总数,返回值是 long​ 类型。

示例
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
long count = numbers.stream().count();

image-20230630000607685

reduce

Reduce​ 中文翻译为:减少、缩小。通过入参的 Function​,我们能够将 list​ 归约成一个值。它的返回类型是 Optional​ 类型。

示例
import java.util.stream.Stream;

public class StreamReduceExample {
    public static void main(String[] args) {
        // 创建一个整数流
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);

        // 使用reduce求和
        // 这里的reduce方法将流中的元素累加起来
        // 初始值是0,accumulator是两个整数相加的操作符
        int sum = stream.reduce(0, (a, b) -> a + b);

        System.out.println("Sum: " + sum);  // 输出: Sum: 15
    }
}

Optional<String> reduced =
    stringCollection
        .stream()
        .sorted()
        .reduce((s1, s2) -> s1 + "#" + s2);

reduced.ifPresent(System.out::println);
// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"

findFirst​​​

findFirst​ 方法是 Stream API 中的一个终端操作(terminal operation),用于从 Stream 中查找并返回第一个元素。如果 Stream 为空,findFirst​ 方法将返回一个空的 Optional​ 对象。

示例
List<String> list = Arrays.asList("apple", "banana", "cherry");
Optional<String> firstElement = list.stream().findFirst();
List<String> myList = Arrays.asList("Java", "Kotlin", "Scala", "Groovy");
Optional<String> firstElement = myList.stream().findFirst();

// 使用 Optional 的 ifPresent 方法来处理找到的元素
firstElement.ifPresent(element -> System.out.println("The first element is: " + element));

// 如果需要获取 Optional 中的值,并处理空值情况
String first = firstElement.orElse("No elements"); // 如果没有元素,返回 "No elements"
System.out.println("The first element or default is: " + first);

match 匹配

Java Stream API 中的 match​ 方法是一个短路的终端操作,它用于检查 Stream 中的元素是否满足特定的条件。

  1. allMatch
    • allMatch​ 方法用于检查流中的所有元素是否都满足给定的条件。

    • 它接收一个 Predicate​ 参数,返回一个 boolean​ 值。

    • 如果流中的每个元素都满足条件,则返回 true​;否则返回 false​。

    • 例如:

      boolean allPositive = list.stream().allMatch(x -> x > 0);
      
  2. anyMatch
    • anyMatch​ 方法用于检查流中是否至少有一个元素满足给定的条件。

    • 同样接收一个 Predicate​ 参数,返回一个 boolean​ 值。

    • 如果流中至少有一个元素满足条件,则返回 true​;否则返回 false​。

    • 例如:

      boolean anyNegative = list.stream().anyMatch(x -> x < 0);
      
  3. noneMatch
    • noneMatch​ 方法用于检查流中是否所有元素都不满足给定的条件。

    • 也接收一个 Predicate​ 参数,返回一个 boolean​ 值。

    • 如果流中没有任何元素满足条件,则返回 true​;否则返回 false​。

    • 例如:

      boolean noneNegative = list.stream().noneMatch(x -> x >= 0);
      

这三个方法都是短路操作,即在满足条件时会立即停止遍历流,提高了效率。

image-20230630000319642

max

它用于从流中查找最大元素,具体的比较方式由传入的 Comparator​ 决定。

  1. max(Comparator) ​:接受一个 Comparator​ 接口的实现作为参数,用于定义元素之间的比较逻辑。如果 Stream 不为空,返回比较后的“最大”元素;如果为空,则抛出 NoSuchElementException​。
  2. max() ​:这是 max(Comparator)​ 的简化版本,它使用元素的自然顺序来确定最大值。同样,如果 Stream 不为空,返回最大元素;如果为空,抛出 NoSuchElementException​。

示例

使用自然顺序

List<Integer> numbers = Arrays.asList(3, 5, 1, 2);
Optional<Integer> maxNumber = numbers.stream().max(Integer::compareTo);
maxNumber.ifPresent(System.out::println); // 输出最大值 5

使用自定义比较器

List<String> words = Arrays.asList("apple", "banana", "cherry");
Optional<String> longestWord = words.stream().max(Comparator.comparingInt(String::length));
longestWord.ifPresent(System.out::println); // 输出最长的单词 "banana"

处理空的 Stream

List<Integer> emptyList = Collections.emptyList();
Optional<Integer> maxNumber = emptyList.stream().max(Integer::compareTo);
maxNumber.ifPresentOrElse(
    System.out::println,  // 如果有最大值,打印它
    () -> System.out.println("The list is empty, no max value.") // 如果为空,打印消息
);

maxId = activityOrderList.stream().map(CombinedBuyingOrderDO::getId).max(Long::compare).get();

min() :返回 Stream 中的最小值。
示例
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5);
Optional<Integer> min = numbers.stream().min(Integer::compareTo);

2.5其它

2.5.1concat(合并)—静态方法

Stream.concat​ 是一个静态方法,用于将两个独立的流连接起来。

示例
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        Stream<Integer> stream1 = Stream.of(1, 2, 3);
        Stream<Integer> stream2 = Stream.of(4, 5, 6);

        Stream<Integer> concatenatedStream = Stream.concat(stream1, stream2);
        concatenatedStream.forEach(System.out::println);
        // 输出: 1, 2, 3, 4, 5, 6
    }
}

static Stream concat (Stream a,Stream b): 合并a 和 b两个流为一个流

ArrayList<String> list = new ArrayList<>();
		list.add("张三丰");
		list.add("张无忌");
		list.add("张翠山");
		list.add("王二麻子");

		ArrayList<String> list2 = new ArrayList<>();
		list2.add("张三丰夫人");
		list2.add("张无忌夫人");
		list2.add("张翠山夫人");
		list2.add("王二麻子夫人");
		list2.add("谢广坤夫人");
		list2.add("张良夫人");
		//简化前
		Stream<String> stream1 = list.stream();
		Stream<String> stream2 = list2.stream();

		Stream<String> stream3 = Stream.concat(stream1, stream2);
		stream3.forEach(s -> System.out.println(s));

		//简化后
		Stream.concat(list.stream(),list2.stream()).forEach(s -> System.out.println(s));

image

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

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

相关文章

十年磨一剑,华火电燃组合灶重磅问世,引领厨房新时代

十年磨一剑&#xff0c;华火研发团队经过不懈努力&#xff0c;成功将等离子电生明火技术与电陶炉红外线光波炉技术精妙融合&#xff0c;打造出的这款具有划时代是意义的电燃组合灶HH-SZQP60&#xff0c;终于在 2024 年6月震撼登场&#xff0c;该灶以其卓越的创新技术和独特的产…

day01-项目介绍及初始化-登录页

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 day01-项目介绍及初始化-登录页一、人力资源项目介绍1.1项目架构和解决方案主要模块解决的问题 二、拉取项目基础代码1.引入库2.升级core-js版本到3.25.5按照完整依…

一篇文章带你玩懂数据库的基础函数

数据库的函数 单行函数1.数据函数2.字符串函数3.时间函数4.流程函数 多行函数聚合函数 阅读指南&#xff1a; 本文章讲述了对于数据库的单行和多行函数&#xff0c;如果读者感兴趣&#xff0c;后续我们会更新高级的操作在我们的对于数据库教程的合集中&#xff0c;大家可以来很…

振弦采集仪在大型工程安全监测中的应用探索

振弦采集仪在大型工程安全监测中的应用探索 振弦采集仪是一种用于监测结构振动和变形的设备&#xff0c;它通过采集振弦信号来分析结构的动态特性。在大型工程安全监测中&#xff0c;振弦采集仪具有重要的应用价值&#xff0c;可以帮助工程师和监测人员实时了解结构的状况&…

红队内网攻防渗透:内网渗透之内网对抗:横向移动篇Kerberos委派安全非约束系约束系RBCD资源系Spooler利用

红队内网攻防渗透 1. 内网横向移动1.1 委派安全知识点1.1.1 域委派分类1.1.2 非约束委派1.1.2.1 利用场景1.1.2.2 复现配置:1.1.2.3 利用思路1:诱使域管理员访问机器1.1.2.3.1 利用过程:主动通讯1.1.2.3.2 利用过程:钓鱼1.1.2.4 利用思路2:强制结合打印机漏洞1.1.2.5 利用…

利用Linked SQL Server提权

点击星标&#xff0c;即时接收最新推文 本文选自《内网安全攻防&#xff1a;红队之路》 扫描二维码五折购书 利用Linked SQL Server提权 Linked SQL server是一个SQL Server数据库中的对象&#xff0c;它可以连接到另一个SQL Server或非SQL Server数据源&#xff08;如Oracle&a…

Techviz:XR协作工作流程,重塑远程电话会议新形式

在当今快速发展的数字环境中&#xff0c;无缝远程协作的需求正在成为企业多部门协同工作的重中之重&#xff0c;尤其是对于制造业、建筑和设计等行业的专业人士而言&#xff0c;这一需求更加迫切。传统的远程电话会议协作形式存在着延滞性&#xff0c;已经渐渐跟不上当今快节奏…

脑洞爆裂,OLED透明屏与红酒柜相结合

当OLED透明屏与红酒柜相结合时&#xff0c;我们可以设想一个极具创新性和实用性的产品&#xff0c;将科技美学与品酒文化完美融合。以下是我为这种结合提出的一些创新设想&#xff1a; 透明展示与虚拟标签 透明展示&#xff1a;OLED透明屏能够直接安装在红酒柜的玻璃门或侧面&a…

面试突击指南:Java基础面试题3

1.介绍下进程和线程的关系 进程:一个独立的正在执行的程序。 线程:一个进程的最基本的执行单位,执行路径。 多进程:在操作系统中,同时运行多个程序。 多进程的好处:可以充分利用CPU,提高CPU的使用率。 多线程:在同一个进程(应用程序)中同时执行多个线程。 多线程…

Redis 7.x 系列【9】数据类型之自动排重集合(Set)

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Redis 版本 7.2.5 源码地址&#xff1a;https://gitee.com/pearl-organization/study-redis-demo 文章目录 1. 前言2. 常用命令2.1 SADD2.2 SCARD2.3 SISMEMBER2.4 SREM2.5 SSCAN2.6 SDIFF2.7 SU…

Jmeter,badboy学习

1、注意Jmeter与jdk之间的版本对应 2、Jmeter的作用&#xff1a; jmeter可以做接口测试和压力测试。其中接口测试的简单操作包括做http脚本&#xff08;发get/post请求、加cookie、加header、加权限认证、上传文件&#xff09;、做webservice脚本、参数化、断言、关联&#x…

Polyplus转染试剂的优点,你知道吗?

Polyplus专注于为生命科学研究、体内转染、生物制品制造以及细胞和基因治疗的客户&#xff0c;提供创新的核酸递送解决方案。其深耕转染领域&#xff0c;产品力强劲。在科研领域&#xff0c;其产品转染效果以及价格都优于lipo系列。无论是在基础科学研究中&#xff0c;还是在临…

Node.js下载及安装详细教程

目录 Node.js安装详细教程 下载安装环境变量配置文件结构配置npm在安装全局模块时的路径和缓存cache的路径测试常见命令 Node.js安装详细教程 &#x1f441;官网下载地址:Download | Node.js (nodejs.org) 下载速度慢的话 可以使用网盘下载&#xff1a; https://pan.quark.…

文心一言 VS 讯飞星火 VS chatgpt (290)-- 算法导论21.3 3题

三、给出一个包含 m 个 MAKE-SET 、UNION 和 FINDSET 操作的序列(其中有 n 个是 MAKE-SET 操作)&#xff0c;当仅使用按秩合并时&#xff0c;需要 Ω(mlgn) 的时间。如果要写代码&#xff0c;请用go语言。 文心一言&#xff1a; 在并查集&#xff08;Union-Find&#xff09;数…

RabbitMQ WEB管理端介绍

页面功能概览 Overview(概述)Connections(连接)Channels(通道)Exchanges(交换器)Queues(队列)Admin(用户管理)。 1. Overview(概述) 主要分为三部分 1.1 Queued messages&#xff08;所有队列的消息情况&#xff09; Ready&#xff1a;待消费的消息总数Unacked&#xff1a;待应…

今日好料(网易数据治理白皮书)

今日好料&#xff08;网易数据治理白皮书&#xff09; 在当前的数字经济时代&#xff0c;数据已经成为企业最重要的资产之一。然而&#xff0c;如何管理和利用这些数据&#xff0c;成为企业成功的关键所在。本文将探讨数据治理的概念、重要性及其实施策略&#xff0c;结合网易…

国标GB/T 28181详解:国标GBT28181-2022的目录通知流程

目录 一、定义 二、作用 1、实时同步设备目录状态 2、优化资源管理和调度 3、增强系统的可扩展性和灵活性 4、提高系统的可靠性和稳定性 5、支持多级级联和分布式部署 6、便于用户管理和监控 三、基本要求 1、目录通知满足以下基本要求 2、关键要素 &#xff08;1…

Python调用外部系统命令详细讲解

利用Python调用外部系统命令的方法可以提高编码效率。调用外部系统命令完成后可以通过获取命令执行返回结果码、命令执行的输出结果进行进一步的处理。本文主要描述Python常见的调用外部系统命令的方法&#xff0c;包括os.system()、os.popen()、subprocess.Popen()等。 本文分…

【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【14】缓存与分布式锁

持续学习&持续更新中… 守破离 【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【14】缓存与分布式锁 缓存本地缓存分布式缓存-本地模式在分布式下的问题分布式缓存整合 redis 作为缓存JMeter测试出OutOfDirectMemoryError【堆外内存溢出】 高并发读下缓存失效问题缓存…

操纵系统的特征调度算法

操纵系统的特征 调度算法是操作系统用来决定各个进程/作业在CPU上执行顺序的方法。最常见的调度算法有&#xff1a;FCFS、SJF、HRRN、RR、HPF和MFQ。这集先介绍前三个 先来先服务 FCFS 根据作业到达的先后顺序调度&#xff0c;CPU会一直运行直到作业结束&#xff0c;所以这个…