全面吃透Java Stream流操作,让代码更加的优雅

news2024/11/17 19:31:41

文章目录

  • 1 认识Stream流
    • 1.1 什么是流
    • 1.2 流与集合
      • 1.2.1 流只能遍历一次
      • 1.2.2 外部迭代和内部迭代
    • 1.3 流操作
      • 1.3.1 中间操作
      • 1.3.2 终端操作
      • 1.3.3 使用流
  • 2 学会使用Stream流
    • 2.1 筛选和切片
      • 2.1.1 用谓词筛选
      • 2.1.2 筛选各异的元素
      • 2.1.3 截短流
      • 2.1.4 跳过元素
    • 2.2 映射
      • 2.2.1 map方法
      • 2.2.2 flatMap方法
    • 2.3 查找和匹配
      • 2.3.1 检查谓词是否至少匹配一个元素
      • 2.3.2 检查谓词是否匹配所有元素
      • 2.3.3 查找元素
      • 2.3.4 查找第一个元素
    • 2.4 归约
      • 2.4.1 元素求和
      • 2.4.2 最大值和最小值
    • 2.5 数值流
      • 2.5.1 映射到数值流
      • 2.5.2 转换回对象流
      • 数值范围
    • 2.6 构建流
      • 2.6.1 由值创建流
      • 2.6.2 由数组创建流
      • 2.6.3 由文件生成流
      • 2.6.4 由函数生成流:创建无限流
  • 3 利用Stream流来处理更为复杂的数据处理查询
    • 3.1 collect可以做什么
    • 3.2 使用collect进行归约和汇总
    • 3.3 使用collect进行分组
    • 3.4 使用collect来进行分区

1 认识Stream流

在Java中我们对数组和集合的操作一定是非常多的,几乎每个程序都会制造和处理数组或者集合。在Java 8之前我们处理这些数据时需要迭代、需要判断并且需要写具体的算法才能获取到想要的结果。有了流之后只需要表达想要什么,最终就会得到想要的结果,不必去担心怎么去显示的实现这些算法。

如果要去大量元素,并且想提高性能,就需要做并行处理,利用多核架构。这就需要多线程代码,但多线程的代码是很复杂的,稍有不甚就会带来很多bug。

在Java 8中引入的流就帮我们解决了这些问题,让我们的代码更加的优雅。

1.1 什么是流

Stream流是Java API的新成员,它允许以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)。其实完全可以把它看成一个遍历数据集的高级迭代器。而且还可以并行的去处理数据集,无需写任何多线程代码。

下面我们用一个例子来演示Stream流:
1、创建一个水果类,name表示水果名称,weight表示水果重量。

public class Fruits {
    /**
     * 名称 eg: 香蕉、苹果、橙子
     */
    private String name;

    /**
     * 重量 单位:克(g)
     */
    private Integer weight;

    public Fruits(String name, Integer weight) {
        this.name = name;
        this.weight = weight;
    }

    public String getName() {
        return name;
    }

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

    public Integer getWeight() {
        return weight;
    }

    public void setWeight(Integer weight) {
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "Fruits{" +
                "name='" + name + '\'' +
                ", weight=" + weight +
                '}';
    }
}

2、创建一个list集合存放一些水果类,可以理解这个list集合为果篮。

List<Fruits> list = new ArrayList<>();
list.add(new Fruits("苹果",132));
list.add(new Fruits("苹果",145));
list.add(new Fruits("苹果",120));
list.add(new Fruits("苹果",190));
list.add(new Fruits("香蕉",90));
list.add(new Fruits("香蕉",50));
list.add(new Fruits("香蕉",100));
list.add(new Fruits("西瓜",2000));

3、Java 8之前做如下操作需要这样写代码

List<Fruits> list = getList();
// 1.累加果篮中所有水果的重量
int totalWeight = 0;
for (Fruits fruits : list) {
    totalWeight += fruits.getWeight();
}
System.out.println(totalWeight);
// 2.根据重量对水果排序,使用匿名类
Collections.sort(list, new Comparator<Fruits>() {
    @Override
    public int compare(Fruits o1, Fruits o2) {
        return o1.getWeight().compareTo(o2.getWeight());
    }
});
System.out.println(list);
// 3.获取排序后的水果名称
List<String> fruitsName = new ArrayList<>();
for (Fruits fruits : list) {
    fruitsName.add(fruits.getName());
}
System.out.println(fruitsName);

4、Java 8中我们做如下操作可以这样写代码

List<Fruits> list = getList();
List<String> fruitsName = list.stream()
        .filter(s -> s.getWeight() > 120) // 获取重量大于120g的水果
        .sorted(Comparator.comparing(Fruits::getWeight)) // 按重量从小到大排序
        .map(Fruits::getName) // 获取水果名称
        .collect(Collectors.toList());// 生成新的列表
        
// [苹果, 火龙果, 芒果, 西瓜]
System.out.println(fruitsName);

5、如果我们想使用多核架构并发来执行这段代码,只需要将stream改为parallelStream

List<Fruits> list = getList();
List<String> fruitsName = list.parallelStream()
        .filter(s -> s.getWeight() > 120) // 获取重量大于120g的水果
        .sorted(Comparator.comparing(Fruits::getWeight)) // 按重量从小到大排序
        .map(Fruits::getName) // 获取水果名称
        .collect(Collectors.toList());// 生成新的列表

// [苹果, 火龙果, 芒果, 西瓜]
System.out.println(fruitsName);

总结:

从上面的代码我们发现Stream流让代码变得更简单。其中代码是以声明性的方式写的,只需要说明想要完成什么即可,不需要去写如何实现的代码。而且还可以将几个基础操作链接起来,类似流水线一样的去处理数据。

Stream

1.2 流与集合

集合是一个内存中的数据结构,它包含数据结构中目前所有的值。流则是概念上固定的数据结构(不能添加或删除元素),其元素是按需获取的,使用一个获取一个。

1.2.1 流只能遍历一次

流和迭代器非常的类似,只能遍历一次。遍历完之后,这个流就已经被消费掉了。要先使用的话只能怪从原始数据源那里再获取一个新的流。

List<Fruits> list = getList();
Stream<Fruits> stream = list.stream();
stream.forEach(fruits -> System.out.println(fruits)); // 打印流中元素
// 再次打印则会报错
// java.lang.IllegalStateException: stream has already been operated upon or closed
stream.forEach(s -> System.out.println(s));

1.2.2 外部迭代和内部迭代

对集合使用for,for-each迭代都属于外部迭代,我们需要手动来完成迭代逻辑。而流的迭代属于内部迭代,不需要我们来实现流的迭代逻辑,只管完成逻辑代码就好,流已经替我们把迭代做了。

List<Fruits> list = getList();
// 外部迭代
for (Fruits fruits : list) {
     System.out.println(fruits.getName());
}

// 内部迭代
list.stream()
     .forEach(fruits -> System.out.println(fruits.getName()));

1.3 流操作

在java.util.stream.Stream中的Stream接口中定义了许多操作。我们将这些操作分为两类:中间操作和终端操作。
中间操作和终端操作

1.3.1 中间操作

中间操作都会返回一个符合条件的流,可以紧接着再执行中间操作或终端操作。例如上面我们在falter操作后紧接着执行sorted操作。

操作类型返回类型操作参数函数描述符
filter中间StreamPredicateT -> boolean
map中间StreamFunction<T, R>T -> R
limit中间Stream
sorted中间StreamComparator(T, T) -> int
distinct中间Stream

1.3.2 终端操作

终端操作会生成流的结果,具体的结果根据我们声明的方法来决定。例如我们上面我们通过collect来生成一个List列表。也可以使用forEach来直接打印结果。终端操作后就结束了,不会生成新的流所以也不能再接后续的操作。

操作类型目的
forEach终端消费流中的每个元素并对其应用Lambda。返回void。
count终端返回流中元素个数。这一操作返回long。
collect终端把流归约成一个集合,比如List、Map甚至是Integer。

1.3.3 使用流

流的使用一般包括三件事:

  • 一个数据源(如集合)来执行一个查询
  • 一个中间操作链,形成一条流的流水线
  • 一个终端操作,执行流水线,并能生成结果

2 学会使用Stream流

2.1 筛选和切片

2.1.1 用谓词筛选

Streams接口支持filter方法,此方法接受一个谓词作为参数,返回一个符合谓词的元素的流。

// 筛选重量大于120g的水果
List<Fruits> list = getList();
list.stream()
    .filter(s -> s.getWeight() > 120)
    .forEach(System.out::println);

2.1.2 筛选各异的元素

使用distinct方法对流中的元素去重(根据元素的hashCode和equals方法)。

// 对这个集合中的元素去重
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
        .distinct()
        .forEach(System.out::println);

2.1.3 截短流

使用limit(n)方法,可以返回一个不超过给定长度的流。

// 筛选重量大于120g的水果,选取前三个
List<Fruits> list = getList();
list.stream()
        .filter(s -> s.getWeight() > 120)
        .limit(3)
        .forEach(System.out::println);

2.1.4 跳过元素

skip(n)方法,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。

// 筛选重量大于120g的水果,跳过前三个,返回剩下的
List<Fruits> list = getList();
list.stream()
        .filter(s -> s.getWeight() > 120)
        .skip(3)
        .forEach(System.out::println);

2.2 映射

一个非常常见的数据处理套路就是从某些对象中选择信息。比如在SQL里,你可以从表中选择一列。Stream API也通过map和flatMap方法提供了类似的工具。

2.2.1 map方法

map方法接受一个Function<T, R>类型的函数作为参数,流中的元素T经过这个流会映射为一个新元素R。(这里的映射是转换为新元素,是创建一个新元素而不是去修改原有的元素)。

// 获取流中每个元素的长度并打印
List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action");
words.stream()
        .map(String::length)
        .forEach(System.out::println);

2.2.2 flatMap方法

我们拿一个例子来演示map方法和flatMap方法的不同:我们对集合[“Java 8”, “Lambdas”, “In”, “Action”]进行处理,返回这个列表中涉及到的所有字母,不可以重复。
正确的结果应为:[ ‘J’ , ‘a’ , ‘v’ , ’ ’ , ‘8’ , ‘L’ , ‘m’ , ‘b’ , ‘d’ , ‘s’ , ‘I’ , ‘n’ , ‘A’ , ‘c’ , ‘t’ , ‘i’ , ‘o’]

第一反应我们肯定使用如下代码来解决:

words.stream()
        .map(s -> s.split(""))
        .distinct()
        .forEach(System.out::println);

返回结果:
在这里插入图片描述
这样的结果是因为我们给map输入的为Stream<String>流,split方法返回的是一个数组,所以返回的类型为Stream<String[]>流,打印数组当然就是这样的结果了。

我们可以将Stream<String[]>流使用方法Arrays.toString将数组转换为字符串,然后打印来观察结果:

words.stream()
        .map(s -> s.split(""))
        .distinct()
        .map(Arrays::toString)
        .forEach(System.out::println);

返回结果:
撒反对
我们观察到这完全不是想要的结果。下面用一张图来解释为什么是这样的结果。

jiwshi
我们发现上面的distinct的去重其实是对String[]进行去重的。

下面我们使用flatMap(Arrays::stream)来将Stream<String[]>流转换为Stream<String>来实现想要的结果

List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action");
List<String> list = words.stream()
                .map(s -> s.split(""))
                .flatMap(Arrays::stream)
                .distinct()
                .collect(Collectors.toList());
        System.out.println(list);

返回结果:
在这里插入图片描述
这正是我们想要的结果,我们也用一张图来解释一下这个的原因:
在这里插入图e片描述
一言以蔽之,flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流.

2.3 查找和匹配

2.3.1 检查谓词是否至少匹配一个元素

使用anyMatch方法可以判断流中是否存在一个这样的元素。

// 判断流中是否存在苹果
List<Fruits> list = getList();
boolean b = list.stream().anyMatch(s -> "苹果".equals(s.getName()));
System.out.println(b);

2.3.2 检查谓词是否匹配所有元素

allMatch方法判断流中的所有元素是否都匹配,noneMatch方法判断流中所有的元素都不匹配。这两个方法都用到了“短路”,及发现一个不符合则不再继续进行判断了。

// 判断流中元素重量是否都大于10g
List<Fruits> list = getList();
boolean b = list.stream().allMatch(s -> s.getWeight() > 10);
System.out.println(b);
// 判断流中元素重量是否都不大于(小于)10g
List<Fruits> list = getList();
boolean b = list.stream().noneMatch(s -> s.getWeight() > 10);
System.out.println(b);

2.3.3 查找元素

使用findAny方法可以查找流中符合条件的元素,返回符合条件的任意一个元素。此方法也利用了“短路"操作,找到一个元素即立即结束。

// 找到重量大于100g的任意一元素
List<Fruits> list = getList();
Optional<Fruits> any = list.stream().filter(s -> s.getWeight() > 100).findAny();
System.out.println(any.get());

2.3.4 查找第一个元素

使用findFirst方法可以返回符合条件的第一个元素,findAny是任意一个元素,fingFirst是返回第一个元素,这里需要的流为顺序流(如List或者其他排好序的流)。

// 找到重量大于100g的第一个元素
List<Fruits> list = getList();
Optional<Fruits> any = list.stream().filter(s -> s.getWeight() > 100).findFirst();
System.out.println(any.get());

2.4 归约

到目前为止,你见到过的终端操作都是返回一个boolean(allMatch之类的)、void(forEach)或Optional对象(findAny等)。你也见过了使用collect来将流中的所有元素组合成一个List。

归约就是对我们得到的结果进行统计,例如得到所有水果的重量和,得到重量最高的那个水果等等。

2.4.1 元素求和

使用reduce方法可以对流中所有元素求和

// 对所有元素的重量求和
List<Fruits> list = getList();
Integer reduce = list.stream().map(Fruits::getWeight).reduce(0, Integer::sum);
System.out.println(reduce);

上面reduce方法第一个参数是默认值,可以不填,不填的话会返回一个Optional对象,考虑流中没有任何元素的情况下reduce方法不能求和,所以将结果包含在Optional对象中。

// 对所有元素的重量求和
List<Fruits> list = getList();
Optional<Integer> reduce = list.stream().map(Fruits::getWeight).reduce( Integer::sum);
System.out.println(reduce.get());

2.4.2 最大值和最小值

可以使用reduce来计算流中的最大值或者最小值

// 得到所有元素中最大的重量的值
List<Fruits> list = getList();
Optional<Integer> reduce = list.stream().map(Fruits::getWeight).reduce( Integer::max);
System.out.println(reduce.get());
// 得到所有元素中最小的重量的值
List<Fruits> list = getList();
Optional<Integer> reduce = list.stream().map(Fruits::getWeight).reduce( Integer::min);
System.out.println(reduce.get());

2.5 数值流

Java 8引入了三个原始类型特化流接口来解决这个问题:IntStream、DoubleStream和LongStream,分别将流中的元素特化为int、long和double,从而避免了暗含的装箱成本。每个接口都带来了进行常用数值归约的新方法,比如对数值流求和的sum,找到最大元素的max。

2.5.1 映射到数值流

IntStream中含有特定方法执行归约操作,例如sum方法还有max、min、average方法等。sum方法默认返回的是0,而max、min和average方法返回的为OptionalInt等对应的方法。

// 得到所有元素重量和
List<Fruits> list = getList();
Integer sum = list.stream().map(Fruits::getWeight).reduce(0, Integer::sum); // 这里暗含了装箱的成本
IntStream intStream = list.stream().mapToInt(Fruits::getWeight); // 转换为IntStream流
int sum1 = intStream.sum();// IntStream流含有特定的sum方法可以直接得出和
System.out.println(sum);
System.out.println(sum1);

2.5.2 转换回对象流

List<Fruits> list = getList();
Integer sum = list.stream().map(Fruits::getWeight).reduce(0, Integer::sum);
IntStream intStream = list.stream().mapToInt(Fruits::getWeight);
Stream<Integer> boxed = intStream.boxed(); // 转换会对象流

数值范围

IntStream和LongStream中含有2个方法range和rangeClosed来帮助生成范围内的数字。他们的参数都是第一个为起始值,第二个为结束值,但range是不包含结束值的,rangeClosed是包含结束值的。

int sum = IntStream.range(0, 100).filter(s -> s % 2 == 0).sum();
System.out.println(sum);

2.6 构建流

2.6.1 由值创建流

使用静态方法Stream.of,通过显式值创建一个流。

Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);

可以使用Stream.empty()来创建一个空流。

2.6.2 由数组创建流

可以使用Arrays。stream(数组)将一个数组转换为流。

int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
System.out.println(sum);

2.6.3 由文件生成流

Java中用于处理文件等I/O操作的NIO API(非阻塞 I/O)已更新,以便利用Stream API。java.nio.file.Files中的很多静态方法都会返回一个流。例如,一个很有用的方法是Files.lines,它会返回一个由指定文件中的各行构成的字符串流。

        long uniqueWords = 0;
        try(Stream<String> lines =
                    Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){
            uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
                    .distinct()
                    .count();
        }
        catch(IOException e){

        }

2.6.4 由函数生成流:创建无限流

Stream API提供了两个静态方法来从函数生成流:Stream.iterate和Stream.generate。这两个操作可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流。由iterate和generate产生的流会用给定的函数按需创建值,因此可以无穷无尽地计算下去!一般来说,应该使用limit(n)来对这种流加以限制,以避免打印无穷多个值。
1、迭代

Stream.iterate(0, n -> n + 2)
        .limit(10)
        .forEach(System.out::println);

iterate方法接受一个初始值(在这里是0),还有一个依次应用在每个产生的新值上的Lambda(UnaryOperator类型)。这里,我们使用Lambda n -> n + 2,返回的是前一个元素加上2。

2、生成
与iterate方法类似,generate方法也可让你按需生成一个无限流。但generate不是依次对每个新生成的值应用函数的。它接受一个Supplier类型的Lambda提供新的值。

Stream.generate(Math::random)
        .limit(5)
        .forEach(System.out::println);

请注意,因为你处理的是一个无限流,所以必须使用limit操作来显式限制它的大小;否则,终端操作将永远计算下去。

3 利用Stream流来处理更为复杂的数据处理查询

前面我们已经知道可以使用collect(Collectors.toList())来讲流中的元素结合成一个List。其实collect和reduce一样都是一个归约操作。可以接受一个参数将流中的元素汇总为一个结果。
reduce是接受一个BinaryOperator作为谓词,collect是接受一个Collector接口作为谓词。

3.1 collect可以做什么

还是利用我们之前的果篮List,我们可以:

  • 按不同的水果进行分类,并获
  • 取该水果的总重量。 将水果分为两类,重的和轻的并返回他们的总重量。
  • 可以进行多级分组,先按不同水果分类,再将同一种水果分为重得和轻的。

重新定义果篮集合

List<Fruits> list = new ArrayList<>();
list.add(new Fruits("苹果",132));
list.add(new Fruits("火龙果",145));
list.add(new Fruits("火龙果",120));
list.add(new Fruits("苹果",190));
list.add(new Fruits("火龙果",90));
list.add(new Fruits("苹果",50));
list.add(new Fruits("火龙果",100));
list.add(new Fruits("火龙果",200));

将果篮中的不同水果区分出来

// java 8之前区分
Map<String,List<Fruits>> map = new HashMap<>();
for (Fruits fruits : list) {
    if (map.containsKey(fruits.getName())) {
        List<Fruits> fruitsList = map.get(fruits.getName());
        fruitsList.add(fruits);
        map.put(fruits.getName(),fruitsList);
    } else {
        List<Fruits> fruitsList = new ArrayList<>();
        fruitsList.add(fruits);
        map.put(fruits.getName(), fruitsList);
    }
}
// java 8中使用collect区分
Map<String, List<Fruits>> collectMap = list.stream()
        .collect(Collectors.groupingBy(Fruits::getName));

从上面的代码中我们看到了使用collect来做区分的便利性,我们只需要表达出想要什么,完全不需要去考虑该如何做。

3.2 使用collect进行归约和汇总

Collectors接口为我们已经定义了许多有用的默认方法,我们可以直接使用。

1、统计果篮(List)中水果的数量

// 使用collect进行统计
Long collect = list.stream().collect(Collectors.counting());
// 可以直接使用count来统计
long count = list.stream().count();

2、查询果篮中最重和最轻的水果
需要利用Collectors.maxBy方法,参数为Comparator比较器。

// 获取最重的水果
List<Fruits> list = getList();
Comparator<Fruits> fruitsComparator = Comparator.comparingInt(Fruits::getWeight);
Optional<Fruits> maxFruits = list.stream().collect(Collectors.maxBy(fruitsComparator));
System.out.println(maxFruits.get());
// 获取最轻的水果
List<Fruits> list = getList();
Comparator<Fruits> fruitsComparator = Comparator.comparingInt(Fruits::getWeight);
Optional<Fruits> maxFruits = list.stream().collect(Collectors.minBy(fruitsComparator));
System.out.println(maxFruits.get());

3、求果篮中水果的全部重量,平均重量,最大值,最小值,数量信息
Collectors类提供了工厂方法Collectors.summingInt来求和,summingInt将一个对象映射为求和所需的int函数,传递给collect方法执行来获取最终结果。

// 如果流为空则返回0,默认值为0
Integer collect = list.stream().collect(Collectors.summingInt(Fruits::getWeight));
System.out.println(collect);

类似的方法还有Collectors.summingLong和Collectors.summingDouble,他们作用都是一样的,用于字段为long和double的情况。

Collectors类还提供了一个更为强大的方法summarizingInt,他可以帮我求出这个流的总和、平均值、最大值和最小值,会将结果存放在作IntSummaryStatistics的类里,获取我们想要的结果即可。

IntSummaryStatistics fruitsTotal = list.stream().collect(Collectors.summarizingInt(Fruits::getWeight));
System.out.println(fruitsTotal.getMax());
System.out.println(fruitsTotal.getSum());
System.out.println(fruitsTotal.getCount());
System.out.println(fruitsTotal.getAverage());
System.out.println(fruitsTotal.getMin());

都有对应Long和Double的方法。

4、连接字符串
joining工厂方法返回的收集器会把对流中每一个对象应用toString方法得到的所有字符串连接成一个字符串。

String collect = list.stream().map(Fruits::getName).collect(Collectors.joining());
System.out.println(collect);

该字符串的可读性并不好。joining工厂方法有一个重载版本可以接受元素之间的分界符

String collect = list.stream().map(Fruits::getName).collect(Collectors.joining(","));
System.out.println(collect);

上面我们见到的收集器,其实都可以使用Collectors.reducing方法来完成,之前写的方法都是为了方便。

// 对果篮中水果重量求和,第一个参数为初始值,第二参数为转换函数,第三个参数BinaryOperator将两个元素累加
Integer collect = list.stream().collect(Collectors.reducing(0, Fruits::getWeight, (i, j) -> i + j));
System.out.println(collect);
// 找到重的水果
Optional<Fruits> collect = list.stream().collect(Collectors.reducing((i, j) -> i.getWeight() > j.getWeight() ? i : j));
System.out.println(collect.get());

3.3 使用collect进行分组

分组就是将不同类型的物品分别放到不同的组,例如在果篮(List)中可以将苹果分为一个组,火龙果分为另一个组。

// 将不同名称的元素分为不同的组
Map<String, List<Fruits>> collect = list.stream().collect(Collectors.groupingBy(Fruits::getName));
System.out.println(collect);

上面我们根据元素的名称来分组,相同名称的分为同一个组,使用Collectors.groupingBy可以帮我们完成这样的操作,最终返回的是Map<String, List>类型。

也可以根据重量来分组,小于70g的分为一组,大于等于70小于150g的分为一组,大于等于150g的分为一组。

// 将不同重量的元素分为不同的组
Map<String, List<Fruits>> collect = list.stream().collect(Collectors.groupingBy(fruits -> {
    if (fruits.getWeight() < 50) {
        return "小于50g";
    } else if (fruits.getWeight() >= 50 && fruits.getWeight() < 150) {
        return "大于等于50g小于150g";
    } else {
        return "大于等于150g";
    }
}));
System.out.println(collect);

多级分组

多级分组就是可以先按名称分组再按重量分组,可以利用Collectors.groupingBy的另一个版本,他可以接受2个参数,第二个参数传递另一个分组方式来进行多级分组。

Map<String, Map<String, List<Fruits>>> collect = list.stream().collect(Collectors.groupingBy(Fruits::getName, Collectors.groupingBy(fruits -> {
    if (fruits.getWeight() < 50) {
        return "小于50g";
    } else if (fruits.getWeight() >= 50 && fruits.getWeight() < 150) {
        return "大于等于50g小于150g";
    } else {
        return "大于等于150g";
    }
})));
System.out.println(collect);

第二个参数其实可以不是Collectors.groupingBy方法的,也可以是Collectors.counting方法来收集数量。

Map<String, Long> collect = list.stream().collect(Collectors.groupingBy(Fruits::getName, Collectors.counting()));
System.out.println(collect);

还可以过去不同元素中最大的元素是哪个

Map<String, Optional<Fruits>> collect = list.stream().collect(Collectors.groupingBy(Fruits::getName, Collectors.maxBy(Comparator.comparingInt(Fruits::getWeight))));
System.out.println(collect);

我们发现元素都是使用Optional类包起来的,这样防止没有元素也不会产生异常的问题。

因为分组操作的Map结果中的每个值上包装的Optional没什么用,所以你可能想要把它们去掉。要做到这一点,或者更一般地来说,把收集器返回的结果转换为另一种类型,你可以使用Collectors.collectingAndThen工厂方法返回的收集器。

Map<String, Fruits> collect = list.stream()
        .collect(Collectors.groupingBy(Fruits::getName,
                Collectors.collectingAndThen(
                        Collectors.maxBy(Comparator.comparingInt(Fruits::getWeight)), 
                        Optional::get)));
System.out.println(collect);

3.4 使用collect来进行分区

分区就是分为2个区,一个true一个false,例如按重量分,小于100g的为一个区,其他的为另一个区。

Map<Boolean, List<Fruits>> collect = list.stream().collect(Collectors.partitioningBy(fruits -> fruits.getWeight() < 100));
System.out.println(collect);

上面返回的集合key分别为true和false。

将分区和分组结合起来使用,先将果篮分为小于100g的一个区和其他区,再按名字分组。

Map<Boolean, Map<String, List<Fruits>>> collect = list.stream().collect(Collectors.partitioningBy(fruits -> fruits.getWeight() < 100, Collectors.groupingBy(Fruits::getName)));
System.out.println(collect);

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

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

相关文章

Java经典面试题——对比 Vector、ArrayList、LinkedList 有何区别?

典型回答 这三者都是实现集合框架中的 List &#xff0c;也就是所谓的有序集合&#xff0c;因此具体功能也比较近似&#xff0c;比如都提供按照位置进行定位、添加或者删除的操作&#xff0c;都提供迭代器以遍历其内容等。但因为具体的设计区别&#xff0c;在行为、性能、线程…

详解CRC原理以及C语言实现

CRC检验原理 CRC&#xff08;Cyclic Redundancy Check&#xff09;校验是一种常用的数据校验方法&#xff0c;它通过计算数据的校验码来检测数据在传输过程中是否出现了错误。 CRC校验的基本原理是将数据按照一定的规则进行计算&#xff0c;得到一个固定长度的校验码&#xf…

JavaScript内改变this指向

之前我们说的都是代码内 this 的默认指向 今天我们要来说一下如何能改变 this 指向 也就是说, 你指向哪我不管, 我让你指向哪, 你就得指向哪 开局 在函数的原型( Function.prototype ) 上有三个方法 call apply bind 既然是在函数的原型上, 那么只要是函数就可以调用这三个方…

React(七):Router基本使用、嵌套路由、编程式导航、路由传参、懒加载

React&#xff08;七&#xff09;一、React-Router的基本使用1.安装和介绍2.路由的配置和跳转3.Navigate的使用4.如果找不到对应的路由路径&#xff1f;二、嵌套路由的用法三、编程式路由导航1.类组件中使用useNavigate2.函数式组件中使用useNavigate四、路由跳转传参1.设置好路…

小白学Pytorch 系列--Torch API(1)

小白学Pytorch 系列–Torch API Torch version 1.13 Tensors TORCH.IS_TENSOR 如果obj是PyTorch张量&#xff0c;则返回True。 注意&#xff0c;这个函数只是简单地执行isinstance(obj, Tensor)。使用isinstance 更适合用mypy进行类型检查&#xff0c;而且更显式-所以建议使…

【计组】主存储器有关知识梳理

一、主存储器 主存储器可以直接和CPU进行通信&#xff0c;但是只能保存临时数据&#xff0c;在断电后数据就消失。还有一个特点是&#xff0c;主存储器的容量小&#xff0c;速度快&#xff0c;造价高。 1.构成 2.主存中存储体的构造 最小的存储单位是存储元&#xff0c;存储元…

近亿美元:人工心脏龙头永仁心医疗完成超大额A轮融资

近日&#xff0c;永仁心医疗器械有限公司&#xff08;以下简称“永仁心医疗”&#xff09;完成近一亿美元A轮融资&#xff0c;由北京科兴中维生物技术有限公司&#xff08;SINOVAC科兴&#xff09;领投&#xff0c;太平&#xff08;深圳&#xff09;医疗健康产业私募股权投资基…

腾讯IM h5版本,在安卓原生和IOS原生支持情况的调查以及踩坑、解决办法

介绍 公司准备基于腾讯IM进行开发即时通讯功能&#xff0c;想用H5来开发&#xff0c;这样方便以后移植&#xff0c;在原生app里直接加载&#xff0c;通过三天时间的调查&#xff0c;以及与腾讯客服&#xff0c;技术来回沟通&#xff0c;已经有一定的成果&#xff0c;现将调查成…

【Redis】Redis持久化(一)

目录 1.Redis持久化 1.1.RDB持久化 1.1.1.执行时机 1.1.2.RDB原理 1.1.3.小结 1.2.AOF持久化 1.2.1.AOF原理 1.2.2.AOF配置 1.2.3.AOF文件重写 1.3.RDB与AOF对比 1.Redis持久化 Redis有两种持久化方案&#xff1a; RDB持久化 AOF持久化 1.1.RDB持久化 RDB全称Red…

SpringBoot - 什么是跨域?如何解决跨域?

什么是跨域&#xff1f; 在浏览器上当前访问的网站&#xff0c;向另一个网站发送请求&#xff0c;用于获取数据的过程就是跨域请求。 跨域&#xff0c;是浏览器的同源策略决定的&#xff0c;是一个重要的浏览器安全策略&#xff0c;用于限制一个 origin 的文档或者它加载的脚本…

Doris集成其他系统——ODBC外部表

Doris集成其他系统——ODBC外部表 文章目录Doris集成其他系统——ODBC外部表0. 写在前面1. 正文1.1 ODBC外部表介绍1.2 使用方式2. 使用 ODBC 的 MySQL 外表2.1 前置芝士2.2 安装 unixODBC2.3 安装MySQL 对应版本的 ODBC2.3.1 安装方式2.3.2 检查安装结果2.3.3 其他节点的安装及…

prometheus 配置服务器监控、服务监控、容器中服务监控与告警

最近公司有几个服务遇到了瓶颈&#xff0c;也就是数据量增加了&#xff0c;没有人发现&#xff0c;这不是缺少一个监控服务和告警的系统吗&#xff1f; 主要需求是监控每个服务&#xff0c;顺带监控一下服务器和一些中间件&#xff0c;这里采集的2种&#xff0c;zabbix和prom…

Kafka 消费者组

Kafka 消费者组Consumer数位移重平衡消费者组 (Consumer Group) : 可扩展且容错性的消费者机制 一个组内可能有多个消费者 (Consumer Instance) : 共享一个公共 ID (Group ID)组内的所有消费者协调消费订阅主题 (Subscribed Topics) 的所有分区 (Partition)每个分区只能由同个…

【1】熟悉刷题平台操作

TestBench使用 与quartus中testbench的写法有些许。或者说这是平台特有的特性&#xff01;&#xff01; 1 平台使用谨记 &#xff08;1&#xff09;必须删除&#xff1a;若设计为组合逻辑&#xff0c;需将自动生成的clk删除 若不删除&#xff0c;会提示运行超时错误。 &#…

git推送指定的提交到远程分支详细方法

默认情况下&#xff0c;git push会推送暂存区所有提交&#xff08;也即HEAD及其之前的提交&#xff09;到远程库&#xff0c;实际开发中可能因为功能没有开发完成&#xff0c;但是又必须提交部分修改好的代码就需要用到推送指定commit到远程分支。第一种方式&#xff1a;即符合…

CSS流动布局-页面自适应

项目中经常会碰到页面自适应的问题&#xff0c;例如&#xff1a;商城的列表展示、分类列表展示等页面&#xff0c;如下&#xff1a; 该页面会随着页面的放大缩小而随之发生变化&#xff0c;这种自适应的页面布局在大屏幕、小屏幕、不同的浏览器设备上都应该呈现出与设计匹配的…

【STM32MP157应用编程】4.串口接收、发送数据

目录 串口文件 指令操作串口 程序操作串口 程序说明 程序代码 4_ChuanKou_2.c 启动交叉编译工具 编译 拷贝到开发板 测试 串口文件 在/dev目录下&#xff0c;存放了串口的文件。 文件名对应的串口ttySTM0CH340ttySTM1com2&#xff08;公头&#xff09;ttySTM2com1&a…

java版云HIS系统源码 微服务架构支持VUE

云his系统源码 一个好的HIS系统&#xff0c;要具有开放性&#xff0c;便于扩展升级&#xff0c;增加新的功能模块&#xff0c;支撑好医院的业务的拓展&#xff0c;而且可以反过来给医院赋能&#xff0c;最终向更多的患者提供更好地服务。 私信了解更多&#xff01; 本套基于…

【C语言】结构体和共用体

目录一、结构体&#xff08;一&#xff09;结构体声明&#xff08;二&#xff09;结构体变量定义&#xff08;三&#xff09;结构体变量的初始化&#xff08;四&#xff09;结构体的引用&#xff08;五&#xff09;结构体数组二、共用体&#xff08;一&#xff09;共用体定义&a…

全球商城库存系统架构设计与实践

业务背景 商城原库存系统耦合在商品系统&#xff0c;考虑到相关业务逻辑复杂度越来越高&#xff0c;库存做了服务拆分&#xff0c;在可售库存管理的基础上新增了实物库存管理、秒杀库存、物流时效 、发货限制、分仓管理等功能&#xff0c;满足了商城库存相关业务需求。 系统架构…