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

news2024/11/20 18:39:22

一、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.coloradmin.cn/o/1863238.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【redis】redis概述

1、定义 Redis&#xff08;Remote Dictionary Server&#xff09;&#xff0c;即远程字典服务&#xff0c;是一个开源的、内存中的数据结构存储系统。redis是一个key-value存储系统。和Memcached类似&#xff0c;它支持存储的value类型相对更多&#xff0c;包括string(字符串)…

电脑开机启动项在哪里设置?3个方法教你轻松找到!

“有朋友知道电脑开机启动项在哪里设置吗&#xff1f;我想在里面结束一些程序&#xff0c;但是不知道怎么找到这个功能&#xff0c;请大家帮帮我&#xff01;” 电脑开机启动项的设置对于优化系统启动速度、管理后台运行程序具有重要意义。通过合理配置启动项&#xff0c;我们可…

《Windows API每日一练》6.2 客户区鼠标消息

第五章已经讲到&#xff0c;Windows只会把键盘消息发送到当前具有输入焦点的窗口。鼠标消息则不同&#xff1a;当鼠标经过窗口或在窗口内被单击&#xff0c;则即使该窗口是非活动窗口或不带输入焦点&#xff0c; 窗口过程还是会收到鼠标消息。Windows定义了 21种鼠标消息。不过…

github无法访问,下载慢的解决方法

GitHub是一个存储分享无数的开源项目和代码的宝库网站。然而&#xff0c;由于一些原因&#xff0c;国内用户在访问GitHub时常常遭遇无法访问或下载速度缓慢的问题。这不仅影响了开发者的工作效率&#xff0c;也使一些想要访问下载github文件的普通用户遇到困难。下面小编就来和…

什么是CMSIS || 标准库与HAL库

一&#xff0c;ARM&#xff08;Cortex Microcontroller Software Interface Standard&#xff09; ARM Cortex™ 微控制器软件接口标准&#xff08;Cortex Microcontroller Software Interface Standard&#xff09;是 CortexM 处理器系列的与供应商无关的硬件抽象层。…

Access Levels in Swift

Access Levels (访问级别) Swift provides six different access levels for entities(实体) within your code. These access levels are relative to the source file in which an entity is defined, the module(模块) that source file belongs to, and the package that …

hive架构详解:HQL案例解析(第15天)

系列文章目录 一、Hive基础架构&#xff08;重点&#xff09; 二、Hive数据库,表操作&#xff08;重点&#xff09; 三、Hadoop架构详解(hdfs)&#xff08;补充&#xff09; 四、Hive环境准备&#xff08;操作&#xff09;(补充) 文章目录 系列文章目录前言一、Hive基础架构1、…

【Excel】单元格如何设置可选项、固定表头

设置可选项 固定表头&#xff1a;视图---冻结窗口

Python | Leetcode Python题解之第198题打家劫舍

题目&#xff1a; 题解&#xff1a; class Solution:def rob(self, nums: List[int]) -> int:if not nums:return 0size len(nums)if size 1:return nums[0]first, second nums[0], max(nums[0], nums[1])for i in range(2, size):first, second second, max(first nu…

wps的domain转为shp矢量

wps的namelist制作、python出图和转矢量 简介 wps&#xff08;WRF Preprocessing System&#xff09;是中尺度数值天气预报系统WRF(Weather Research and Forecasting)的预处理系统。 wps的安装地址在GitHub上&#xff1a;https://github.com/wrf-model/WPS 下载完成后&…

循环神经网络——RNN

循环神经网络 在之前NLP基础章节-语言模型中我们介绍了 n n n 元语法&#xff0c;其中单词 x t x_t xt​ 在时间步 t t t 的条件概率仅取决于前面 n n n 个单词&#xff0c;若是想要将之前单词的影响也加入那么模型参数数量会指数级增长。但是可能之前的单词存在重要的信息…

Linux-笔记 高级I/O操作

前言 I/O&#xff08;Input/Output&#xff0c;输入/输出&#xff09;是计算机系统中的一个重要组成部分&#xff0c;它是指计算机与 外部世界之间的信息交流过程。I/O 操作是计算机系统中的一种基本操作&#xff0c;用于向外部设备&#xff08;如 硬盘、键盘、鼠标、网络等&am…

服务器数据恢复—异常断电导致RAID6阵列中磁盘出现坏扇区的数据恢复案例

服务器存储数据恢复环境&#xff1a; 一台存储中有一组由12块SAS硬盘组建的RAID6磁盘阵列&#xff0c;划分为一个卷&#xff0c;分配给几台Vmware ESXI主机做共享存储。该卷中存放了大量Windows虚拟机&#xff0c;这些虚拟机系统盘是统一大小&#xff0c;数据盘大小不确定&…

服务器硬件及RAID配置

目录 一、RAID磁盘阵列 1.概念 2.RAID 0 3.RAID 1 4.RAID 5 5.RAID 6 6.RAID 10 二、阵列卡 1.简介 2.缓存 三、创建 1.创建RAID 0 2.创建RAID 1 3.创建RAID 5 4.创建RAID 10 四、模拟故障 一、RAID磁盘阵列 1.概念 &#xff08;1&#xff09;是Redundant Array …

求任意方阵每行,每列,两对角线上元素之和

注&#xff1a;其中对角线&#xff0c;我们可以分为正副两个&#xff0c;正&#xff1a;左上角指向右下角&#xff0c;副&#xff1a;右上角指向左下角 //这里我们以阶层为5为例子进行代码的实现 #define N 5 void arr_diagonal(int arr[N][N]) {int sum1 0, sum2 0, sum 0…

Js逆向爬虫基础篇

这里写自定义目录标题 逆向技巧断点一 、请求入口定位1. 关键字搜索2. 请求堆栈3. hook4. JSON.stringify 二、响应入口定位&#xff1a;1. 关键字搜索2. hook3. JSON.parse 逆向技巧 断点 普通断点 条件断点 日志断点 XHR断点 一 、请求入口定位 1. 关键字搜索 key关…

C++ | Leetcode C++题解之第198题打家劫舍

题目&#xff1a; 题解&#xff1a; class Solution { public:int rob(vector<int>& nums) {if (nums.empty()) {return 0;}int size nums.size();if (size 1) {return nums[0];}int first nums[0], second max(nums[0], nums[1]);for (int i 2; i < size; …

10.XSS绕过之htmlspecialchars()函数

XSS绕过之htmlspecialchars()函数 首先可以测试一下是否将字符被转移成html实体&#xff0c;输入字符测试 1111"<>$点击提交 查看页面元素代码&#xff0c;发现单引号不变&#xff0c;可以利用 重新输入攻击代码&#xff0c;用单引号闭合前面的&#xff0c;进…

深圳大学 软件测试作业 #2

声明&#xff1a;本人上课摆烂选手&#xff0c;稍微听了下&#xff0c;答案仅供参考。 ———————— 1. 考虑下面这个代码&#xff0c;并回答以下的问题。 (a) 请画出上面代码的控制流程图。(20分) (b) 请画出上面代码的数据流程图。(10分) (c) 找出每个变量的定义使…

SpringBoot整合Mybatis并实现数据库增删改查

写在前面 Mybatis一个基于Java的持久层框架&#xff0c;它通过XML或注解的方式&#xff0c;将SQL语句和Java方法进行映射&#xff0c;使得开发者可以轻松地进行数据库操作。下面我会演示mybatis的配置与使用并实现数据库的增删改查。 1.准备测试数据 使用mybatis实现对数据库…