JDK8 Lambda基本使用

1.什么是Stream

stream是java8中新概念,是用来处理集合、数组查找,筛选和排序等操作。stream可以执行非常复杂的查询,过滤和映射数据等操作。
stream的特点:

  • 不是数据结构,不能存储数据
  • stream不会改变原数据,会将操作结果保存在另一个对象中
  • stream是惰性计算,意思是说只有当前流执行了终止操作符后才会执行

1.1 操作符

操作符是流对数据处理中的一道工序,就好像工厂流水线对产品进行加工处理一样。

1.1.1 使用流程
  1. 首先我们应该使用Stream API来创建一个Stream对象
  2. 对返回的Stream对象进行中间操作,查找,筛选,过滤和映射操作
  3. 最后对Stream进行聚合,制约等操作
1.1.1 操作符分类
  • 中间操作
    • 无状态:指元素的处理不会受到之前元素的影响。
    • 有状态:指该操作只有拿到所有元素后才会继续执行之后操作。
  • 终止操作
    • 非短路操作:指必须处理完所有元素才能返回结果。
    • 短路操作:指找到符号对应条件的元素就终止,就好比喻findFirst()找到集合或数组中第一个满足条件的元素就终止处理。

中间操作

map: 关系映射,把A -> B (一对一),将一个元素映射成另一个流
flatMap: 扁平化操作,可以将一个(多个)对象流映射成流的内容,将生成的单个流合并起来,即扁平化一个流
filter: 过滤操作,将列表或数组过滤不想要的数据
limit: 限流操作,对数据流内容做限制
skip: 跳过操作,跳过某些元素
sorted: 排序操作,对元素排序,前提是对象实现了Comparable接口,也可以自定义比较器
peek: 挑出操作,对某些数据进行编辑修改等

终止操作符

collect: 收集操作,将所有数据收集起来
count: 统计操作, 统计最终数据个数
max, min: 最值操作,求数据的最大值和最小值,需自定义比较器,返回流中最大值或最小值
reduce: 规约操作,将整个数据流中的值,规约成一个值
foreach: 遍历操作
findFirst,findAny: 查找操作,找出第一个符合条件的元素/找出所有符合条件的元素
noneMatch、allMatch、anyMatch: 匹配操作,数据流中是否存在符合条件的元素 返回值为bool 值。
toArray: 数组操作,将生成的数据流转化成数组

1.2 创建Stream

  1. 使用collection下的stream()和 parallelStream() 方法
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); //获取一个顺序流
Stream<String> parallelStream = list.parallelStream(); //获取一个并行流
  1. 使用Arrays中stream方法

DoubleStream stream = Arrays.stream(new double[]{1, 2, 3});

  1. 使用Steam中静态方法of方法

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

1.3 四大函数式接口

1.3.1 Function
**功能型接口**:接受一个参数,返回一个对象。
static <T> Function<T, T> identity() {
     return (var0) -> {
          return var0;
     };
}

ex:

// 创建一个Function对象
Function stringFunction = (Function<String, String[]>) s -> {
     if ("您吃饭了吗".equals(s)) {
         return s.split("");
     }
     return new String[]{"你", "大", "概", "有", "病"};
};
// 调用function的apply方法(apply方法负责接收一个参数,返回一个参数)
System.out.println(Arrays.toString((String[]) stringFunction.apply("我爱你")));
System.out.println(Arrays.toString((String[])stringFunction.apply("您吃饭了吗")));
1.3.2 Supplier
不接受参数,返回一个对象。
public interface Supplier<T> {
    T get();
}

ex:

Supplier<String> supplier = () -> {return "aaa";};
// get方法获取对象
System.out.println(supplier.get());
1.3.3 Comsumer
消费型接口,接收一个参数,没有返回值。

void accept(T var1);
ex:

Consumer consumer = (a) -> {
    System.out.println(a);
};
consumer.accept("嘿嘿嘿");
1.3.4 Predicate
断言操作,接收一个参数,返回boolean值,用于判断,同时提供一个equals方法。

ex:

Predicate<String> predicate = (str)->{return str.isEmpty();};
System.out.println(predicate.test("123"));
System.out.println(predicate.test(""));

2.代码演练

先准备一个列表数据

Car laosilaisiCar = new Car("劳斯莱斯DES", BigDecimal.valueOf(16.5), "Mr.Chen", 5,2);
// cars1列表:一辆劳斯莱斯、大众、别克、野马
List<Car>  cars1 = new ArrayList<Car>() {{
        this.add(laosilaisiCar);
        this.add(new Car("大众A77", BigDecimal.valueOf(9.5), "Mr.Chen", 8,1));
        this.add(new Car("别克EX6", BigDecimal.valueOf(7.5), "Mr.Wang", 10,4));
        this.add(new Car("野马A6", BigDecimal.valueOf(8.5), "Mr.Li", 8,3));
}};
// cars2列表:一辆劳斯莱斯、宾利
List<Car>  cars2 = new ArrayList<Car>() {{
        this.add(laosilaisiCar);
        this.add(new Car("宾利ESA", BigDecimal.valueOf(14.5), "Mr.Li", 6,5));
}};

2.1 中间操作

stream流的一系列操作必须用到终止操作,否则流不会执行

2.1.1 limit

限制列表,可以选择返回集合对象中几条数据。
ex:将cars1列表中的车名打印出来

// 打印出车名
cars1.stream().limit.forEach(System.out::println);
2.1.2 map

map将数据流中元素分别映射其他对象
ex:将car1列表中所有的发明者取出

// 输出结果【Mr.Chen, Mr.Chen, Mr.Wang, Mr.Li, Mr.Zhao】
List<String> devlopers = cars1.stream().map(Car::getDevoloper).collect(Collectors.toList());
2.1.3 flatmap

flatmap扁平化操作,可以拿到多个流的内容映射成一个流,相对于map操作,flatmap可以拿到流更深一层的内容。flatMap 把 input Stream 中的层级结构扁平化,就是将最底层元素抽出来放到一起,最终 output 的新 Stream 里面已经没有 List 了,都是直接的数字。
ex:通过车名连接厂商

// 扁平化操作 汽车对应的汽车公司
List<CarEx> list = cars1.stream().flatMap(car -> carCompanies.stream()
                      .filter(carCompany ->                              car.getCompanyId().equals(carCompany.getId())) 
                    .map(carCompany -> new CarEx(car.getName(), car.getPrice(), car.getDevoloper(), carCompany.getName())))
                    .limit(2)
                    .collect(Collectors.toList());

[FlatMap和Map的区别] https://www.cnblogs.com/candlia/p/11919909.html

2.1.4 mapToDouble

将集合中的指定列转化成Double
ex:对集合的某一列进行求和

// 对每辆车的价格进行求和
Double prizeSum = cars1.stream().mapToDouble(car ->     car.getPrice().doubleValue()).summaryStatistics().getSum();
System.out.println(prizeSum);
2.1.5 distinct

去重操作, 将根据equals 方法进行判断,对数据流中的内容进行去重操作,如果是自定义Bean,需要重写equals和hashcode方法。
ex:对发明者列表去重

// 输出结果:【Mr.Chen, Mr.Wang, Mr.Li, Mr.Zhao】
List<String> devlopers = cars1.stream().map(Car::getDevoloper).distinct().collect(Collectors.toList());
// 对于自定义Bean进行单属性去重(通过OfferId)
List<OfferLogsBo> offerLogsBos = offerLogsPos
        .stream()
        .map(this::covertPo2Bo)
        .collect(Collectors.collectingAndThen(
                Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(OfferLogsBo::getOfferId))), ArrayList::new));
2.1.6 filter

过滤操作,上游不符合条件的数据不会进入到流的下游。
ex:取出陈先生发明的车

// 输出结果:【劳斯莱斯DES, 大众A77】
List<String> mrChenCars = cars1.stream().filter(car -> "Mr.Chen".equals(car.getDevoloper())).map(Car::getName).collect(Collectors.toList());
2.1.7 skip

跳过操作,可以跳过几个元素。

// 跳过前2个元素,输出结果【3】
List<Car> collect2 = cars1.stream().skip(2).collect(Collectors.toList());
2.1.8 peek

peek方法接受一个Comsumer类的入参,可以更改属性值,打印值等其他操作,且返回对象是一个Stream,不需要像foreach那样还要对流的”二次处理“(foreach方法是void返回,因此无法再对数据流进行操作)。peek操作的设计初衷是用来debug调试的。
ex:打印车名集合
List collect1 = cars1.stream().peek(car -> System.out.println(car.getName() + car.getDevoloper())).collect(Collectors.toList());

2.1.9 sorted

sorted传入一个比较器,所以如果我们是按自定义bean的某个属性进行排序的话,那么该属性必须得实现Comparable接口,当然我们也可以自定义比较器。

Stream<T> sorted(Comparator<? super T> var1);


// 按照价格正序:
List<Car> sortByPriceASC = cars1.stream()
    .sorted(Comparator.comparing(Car::getPrice))
    .collect(Collectors.toList());
// 按照价格倒序, 且打印出车名,价格
List<String> nameListAESC = cars1.stream()
    .sorted((o1, o2) -> {
                            if (o1.getPrice().compareTo(o2.getPrice()) >= 0) {
                                return -1;
                            }
                             return 0;
                        })
    .peek(o1 -> o1.setName(o1.getName() + o1.getPrice()))
    .map(Car::getName)
    .collect(Collectors.toList());
// 多字段排序:先按油耗升序,再按价格降序
List<Car> sortByOilAndPriceAESC = cars1.stream()
                .sorted(Comparator.comparing(Car::getOil)
                .thenComparing(Car::getPrice, Comparator.reverseOrder()))
                .collect(Collectors.toList());

2.2 终止操作

2.2.1 foreach

遍历列表。
ex:将cars1列表中的车名打印出来

// 打印出车名
cars1.stream().map(Car::getName).forEach(System.out::println);
2.2.2 findFirst

找出数据流中第一个元素,并返回一个optional对象。
ex:求第一辆满足油升大于8的汽车

// findFirst 求第一辆满足油升大于8的汽车
Car more8OilCarFirst = cars1.stream().filter(car -> car.getOil().compareTo(8) >= 0).findFirst().orElseGet(null);
2.2.3 findAny

随机找出流中一个元素,在串行流下与findFirst没有区别。在并行流模式中,会任意取线程中的一个元素。

Optional<String> stringOptional = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")
     .parallel()
                .findAny(); //在并行流下每次返回的结果可能一样也可能不一样
        stringOptional.ifPresent(System.out::println);
2.2.4 noneMatch, anyMatch, allMatch

noneMatch数据流中没有一个条件匹配的元素。若有匹配的元素,则返回false,反之返回true,我们需要提供一个predicate参数进行断言操作。
allMatch数据流中所有的元素都要满足匹配条件,满足返回true反之返回false。
anyMatch数据流中任意元素满足匹配条件返回true反之返回false。
它们都不是要遍历全部元素才能返回结果。例如 allMatch 只要一个元素不满足条件,就 skip 剩下的所有元素

boolean noneMatch(Predicate<? super T> var1);
boolean anyMatch(Predicate<? super T> var1);
boolean allMatch(Predicate<? super T> var1);

ex:

// 确认如果汽车发明者不包括Mr.Zhen
boolean noMatch = cars1.stream().noneMatch(car -> "Mr.Zhen".equals(car.getDevoloper()));
boolean anyMatch = cars1.stream().anyMatch(car -> "Mr.Chen".equals(car.getDevoloper()));
boolean allMatch = cars1.stream().allMatch(car -> "Mr.Chen".equals(car.getDevoloper()));
2.2.5 max,min

max,min找出数据流中最大/最小的元素,需要传入一个比较器,返回一个Optional对象。
Optional max(Comparator<? super T> var1);
ex:找出汽车列表价格最高的车名
Optional max = cars1.stream().max(Comparator.comparing(Car::getPrice));

2.2.6 reduce

规约操作,将流中元素汇聚成一个值,比如对所有元素求和,乘啊等。
T reduce(T var1, BinaryOperator var2);
我们需要传入一个参数,还有一个BinaryOperator类型的对象
BinaryOperator
BinaryOperator里面有两个静态方法,需要我们传入一个比较器,求最大值和最小值。

static <T> BinaryOperator<T> minBy(Comparator<? super T> var0) {
     Objects.requireNonNull(var0);
     return (var1, var2) -> {
        return var0.compare(var1, var2) <= 0 ? var1 : var2;
     };
}

static <T> BinaryOperator<T> maxBy(Comparator<? super T> var0) {
    Objects.requireNonNull(var0);
    return (var1, var2) -> {
        return var0.compare(var1, var2) >= 0 ? var1 : var2;
    };
}

BinaryOperator<Integer> binaryOperator = (n1, n2) -> n1.compareTo(n2) >= 0  ? n1 : n2;
System.out.println(binaryOperator.apply(3,4));
// 这里实现了一个自定义比较器,虽然调的是minBy方法,但求得是最大值
BinaryOperator<Integer> tBinaryOperator = BinaryOperator.minBy((n1,n2) -> {
        if (n1 > n2) {
            return -1;
        } else if (n1.equals(n2)) {
            return 0;
        } else {
            return 1;
        }
});
System.out.println(tBinaryOperator.apply(1, 4));

[自定义比较器] https://blog.csdn.net/jason_deng/article/details/7026458?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
ex:对车价汇总
BigDecimal prize = cars1.stream().map(Car::getPrice).reduce(BigDecimal.ZERO, BigDecimal::add);
[reduce原理] https://www.cnblogs.com/noteless/p/9511407.html

2.3 生成流

2.3.1 自己控制流的生成
/*
 * 于生成随机数或按照某种自动规则赋值
 * 使用generate生成的stream默认是串行的,因为生成是无限的,所以我们一般会限制生成的流的大小
 */
// 生成10个随机数
Stream.generate(() -> new Random().nextInt()).limit(10).forEach(System.out::println);
2.3.2 iterate

iterate 跟 reduce 操作很像,接受一个种子值,和一个 UnaryOperator(例如 f)。然后种子值成为 Stream 的第一个元素,f(seed) 为第二个,f(f(seed)) 第三个,以此类推。

// 生成一个等比数列
Stream.iterate(1, i -> i * 3).limit(5).forEach(System.out::println);

2.4 分组,分区

Collectors 类的主要作用就是辅助进行各类有用的 reduction 操作,例如转变输出为 Collection,把 Stream 元素进行归组。

2.4.1 分组groupBy

将发明者分组,查看发明者的发明车辆数。

Map<String, List<Car>> collect3 = Stream.concat(cars1.stream(), cars2.stream()).distinct().collect(Collectors.groupingBy(Car::getDevoloper));
        Iterator<Map.Entry<String, List<Car>>> it = collect3.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, List<Car>> next = it.next();
            System.out.println("发明者" + next.getKey() + ", 车数:" + next.getValue().size());
        }
2.4.2 分区partitioningBy

提供一个断言式函数式接口,将满足条件元素归为区,不满足条件的在另一个区

// 方法
public static <T> Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> var0) {
    return partitioningBy(var0, toList());
}
// 取金额大于10的车辆数归组
Map<Boolean, List<Car>> collect4 = Stream.of(cars1, cars2).flatMap(Collection::stream)
                .distinct().collect(Collectors.partitioningBy(car -> car.getPrice().compareTo(BigDecimal.valueOf(10)) >= 0));
        System.out.println("金额大于10的车辆数" + collect4.get(true).size());

2.5 流的拆装箱

// 对一数据Integer数组求和
List<Integer> integers = new ArrayList<Integer>(){};
for (int i = 0; i < 100; i++) {
     integers.add(i);
}
// 1.interger数据流求和
long startTime = System.currentTimeMillis();
Integer result = Stream.of(integers).flatMap(Collection::stream).filter(integer -> integer > 50).reduce(0, Integer::sum);
long endTime = System.currentTimeMillis();
System.out.println("Integer Stream Result => " + result + ", Using Time => " + (endTime - startTime));

// 2. int数据流求和
long startTime2 = System.currentTimeMillis();
int result2 = integers.stream().mapToInt(Integer::intValue).filter(i -> i > 50).reduce(0, Integer::sum);
long endTime2 = System.currentTimeMillis();
System.out.println("Int Stream Result => " + result2 + ", Using Time => " + (endTime2 - startTime2));

运行时长对比:interger数据流运行时间很明显要长于Int数据流,这是为什么呢?
原因:
我们都知道boxing和unboxing操作都是很消耗成本。方式1中reduce(0, Integer::sum)这段代码其实暗含了一个unboxing操作,每个integer元素都必须拆箱成原始的基本类型。JDK8引入了IntStream 、 DoubleStream 和LongStream ,分别将流中的元素特化为 int 、 long 和 double ,从而避免了暗含的拆箱成本。

2.6 流的短路操作

先上代码

List<String> data = Arrays.asList("hello", "world", "hello world");
data.stream()
  .mapToInt(item -> {
         System.out.print(item + ",");
         return item.length();
    })
  .filter(item -> {
         System.out.print(item + ",");
         return item == 5;
    })
  .findFirst()
  .ifPresent(System.out::print);

可能在你看来,上面的代码中流操作是这样进行的:

  1. 遍历流中所有元素并进行mapToInt操作;
  2. 遍历mapToInt操作返回的流中的所有元素并进行filter操作;
  3. 遍历filter操作返回的流中所有元素并进行findFirst操作;
  4. 打印findFirst操作返回的结果。

hello,world,hello world,5,5,11,5
可实际打印结果:
hello,5,5
**原因:**为什么会产生这种结果呢,因为findFirst是短路操作,就好比与||操作符;只要找到一个符合条件的元素就不会继续往下执行了。

2.7 收集器collect

collect是一种可变的汇聚操作,我们可以通过collect将元素输入到可变容器中,当流中元素处理完后,可变容器将累积的结果转化成一个最终的表示。
常见的为toList,toSet,tomap就不多说了

// toMap用法
Map<String, String> map = cars1.stream().collect(Collectors.toMap(Car::getName, Car::getDevoloper));
2.7.1 工作原理

Collector接口有三个泛型,它们的含义如下:

  • T:输入的元素类型
  • A:累积结果的容器类型
  • R:最终生成的结果类型

Collector通过下面四个方法协同工作以完成汇聚操作:

  • supplier: 创建新的结果容器
  • accumulator:将输入元素合并到结果容器中
  • combiner:合并两个结果容器(并行流使用,将多个线程产生的结果容器合并)
  • finisher:将结果容器转换成最终的表示
2.7.2 joining

join方法用于拼接,将结果拼接成一个字符串。Collect工具类为我们提供了两个Joining方法,如下

public static Collector<CharSequence, ?, String> joining(CharSequence var0) {
    return joining(var0, "", "");
}

public static Collector<CharSequence, ?, String> joining(CharSequence var0, CharSequence var1, CharSequence var2) {
   return new Collectors.CollectorImpl(() -> {
       return new StringJoiner(var0, var1, var2);
   }, StringJoiner::add, StringJoiner::merge, StringJoiner::toString, CH_NOID);
}
// 调用StringJoiner方法
/* joining使用方法
 * var0:分割元素
 * var1:字符串前缀
 * var2:字符串后缀
 * 结果:我 + 元素 + 不 + 元素 + 是
 */
String collect = data.stream().collect(Collectors.joining("我", "不", "是"));
2.7.3 运算操作

Collectors分别提供了求平均值averaging、总数couting、最小值minBy、最大值maxBy、求和suming等操作。但是假如你希望将流中结果聚合为一个总和、平均值、最大值、最小值,那么Collectors.summarizing(Int/Long/Double)就是为你准备的,它可以一次行获取前面的所有结果。

// 求平均数
Double collect = integers.stream().collect(Collectors.averagingInt(Integer::intValue));
// 计算
IntSummaryStatistics a = integers.stream().collect(Collectors.summarizingInt(Integer::intValue));

可以调用IntSummaryStatistics提供的getCount,getSum……等获取对应值。
[collect收集器] https://blog.csdn.net/xiliunian/article/details/88773718

2.7.4 二次收集
List<Group> list = new ArrayList(){{
            this.add( new Group("gpu", "1"));
            this.add( new Group("gpu", "2"));
            this.add( new Group("gpu", "3"));
            this.add( new Group("driver", "4"));
            this.add( new Group("driver", "5"));
            this.add( new Group("driver", "6"));

        }};

        // 1.二次收集,计算group分组后各组的元素个数
        Map<String, Long> collect = list.stream().collect(Collectors.groupingBy(
                Group::getGroupCode,
                Collectors.collectingAndThen(
                        Collectors.counting(), Long::new
                )));

        collect.forEach((k,v) -> {
            System.out.println(k +  "    " + v);
        });
        // 2.二次收集,计算group分组后各组元素的最大值
        Map<String, String> collect1 = list.stream().collect(Collectors.groupingBy(
                Group::getGroupCode,
                Collectors.collectingAndThen(
                        Collectors.maxBy(Comparator.comparing(Group::getValue)), v -> v.get().getValue()
                )));
        collect1.forEach((k,v) -> {
            System.out.println(k +  "    " + v);
        });
        // 3.二次收集,计算group分组后逗号分隔拼接各组的元素
        Map<String, String> collect2 = list.stream().collect(Collectors.groupingBy(
                Group::getGroupCode,
                Collectors.collectingAndThen(Collectors.mapping(Group::getValue, Collectors.joining(",")), String::new)
        ));
        collect2.forEach((k,v) -> {
            System.out.println(k +  "    " + v);
        });
        // 4.效果与3等价
        Map<String, String> collect3 = list.stream().collect(Collectors.groupingBy(
                Group::getGroupCode,
                Collectors.mapping(Group::getValue, Collectors.joining(","))));
        // 5.按照algoId分组, 分组后按版本号正序逗号拼接
        Map<Integer,String> collect = dataList.stream().collect(
                Collectors.groupingBy(AlgoVersionData::getAlgoId,
                        Collectors.mapping(AlgoVersionData::getVersion, Collectors.collectingAndThen(Collectors.toCollection(TreeSet::new), b->CollUtil.join(b, ";")))));

        // 
        collect3.forEach((k,v) -> {
            System.out.println(k +  "    " + v);
        });

3.Map合并

3.1 merge

Map.merge方法就是其中一个,merge方法有三个参数,key:map中的键,value:使用者传入的值,remappingFunction:BiFunction函数接口(该接口接收两个值,执行自定义功能并返回最终值)。当map中不存在指定的key时,便将传入的value设置为key的值,当key存在值时,执行一个方法该方法接收key的旧值和传入的value,执行自定义的方法返回最终结果设置为key的值。
源码解析:

default V merge(K var1, V var2, BiFunction<? super V, ? super V, ? extends V> var3) {
    Objects.requireNonNull(var3); // 1  判断入参是否为空
    Objects.requireNonNull(var2); // 2  判断入参是否为空 
    Object var4 = this.get(var1); // 获取调用merge的map对应key的value值:var4=旧值 var2=旧值
    Object var5 = var4 == null ? var2 : var3.apply(var4, var2); // 3 判断map的对应Key的value值是否为空,为空则用新值覆盖,反之则将用给定的重映射函数的结果替换该值。
    if (var5 == null) {  // 如果重映射函数 var3.apply(var4, var2)为空则删除该值
       this.remove(var1);
    } else {            // 否则是新增key = var1, value=重映射函数值
       this.put(var1, var5);
    }
    return var5;
}

实例1:
这段代码意思:创建一个map{name=1},再调用merge方法;如果对应键存在,则在原值上加新值(这里新值=1),若不存在则添加对应的key, value;

public static void main(String[] args) {
    Map<String, Integer> map = new HashMap<>();
    map.put("name", 1);
    map.merge("name", 1, (oldValue, newValue) -> oldValue + newValue);
    merge("count", 1, (oldValue, newValue) -> oldValue + newValue);
    System.out.println(map);
}<br>//返回结果<br>//{count=1, name=2}

实例2:
合并两个map

Map<String, Integer> a = new HashMap<>(3);
a.put("贺武", 59);
a.put("刘雅阁", 98);
a.put("巫一凡", 90);

Map<String, Integer> b = new HashMap<>(3);
b.put("贺武", 77);
b.put("黄小龙", 89);

// 合并两个map, 如果原map b中已有对应的key,则将value相加
a.forEach(
        (key,value) -> b.merge(key, value, Integer::sum)
);
System.out.println(b);

**实战:**求学生的总成绩
1.student类

public class Student {
    private Integer sid;
    private String subject;
    private Integer score;
}

2.求和

List<Student> list = new ArrayList<>();
list.add(new Student(1, "chinese", 10));
list.add(new Student(1, "english", 20));
list.add(new Student(1, "math", 30));
list.add(new Student(2, "chinese", 10));
list.add(new Student(2, "english", 30));
list.add(new Student(2, "math", 30));
list.add(new Student(3, "chinese", 40));
list.add(new Student(3, "english", 40));

Map<Integer, Integer> sumScore = new HashMap<>(3);
list.forEach(student -> sumScore.merge(student.getSid(), student.getScore(), Integer::sum));
System.out.println(sumScore);

3.2 concat

Java 8中的_Stream_ API还可以为我们的问题提供简单的解决方案。首先,我们需要将Map实例组合到一个Stream中。这正是_Stream.concat()_ 操作的作用:

// 当合并的map拥有重复键的时候会抛IllegalStateException
Map<String, Employee> result = combined.collect(
  Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

// 正确用法
Stream<Map.Entry<String, Integer>> combined = Stream.concat(a.entrySet().stream(), b.entrySet().stream());
        combined.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, Integer::sum));
3.3 of

可以通过Stream.of方法将两个map实例转化成统一的流,通过合并规则(相同键新旧值相加)来避免重复键问题

Map<String, Integer> collect = Stream.of(a, b)
    .flatMap(i -> i.entrySet().stream())
    .collect(
             Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, Integer::sum));

Lambda复杂用法

  1. 合并两个Map
// 根据groupCode分组获取map : groupCode  key: optionIds
Map<String, List<String>> map1 = gpudto.getGpus().stream()
            .collect(
                    Collectors.groupingBy(
                           GPUDTO.GPU::getGroupCode, Collectors.mapping(GPUDTO.GPU::getValue, Collectors.toList())));
                           
Map<String, List<String>> map2 = gpudto.getDrivers().stream()
            .collect(
                    Collectors.groupingBy(
                           GPUDTO.Driver::getGroupCode, Collectors.mapping(GPUDTO.Driver::getValue, Collectors.toList())));
        // map 合并 重复key策略:使用map1中的key
        Map<String, List<String>> saveOptions = Stream.of(saveGpuOptions, saveGpuDriverOptions).flatMap(map -> map.entrySet().stream()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (map1, map2) -> map1));

2.按某个字段进行分组,并根据时间进行排序

// 分组排序
Map<Integer, List<AlgoVersionData>> algoId4DataMap = dataList.stream().collect(
                Collectors.groupingBy(AlgoVersionData::getAlgoId,
                    Collectors.collectingAndThen(
                            Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(AlgoVersionData::getCreatedAt))), ArrayList::new)));

// 分组排序,每个组取时间最长的记录                           
Map<Integer, AlgoVersionData> collect = dataList.stream().collect(
                Collectors.groupingBy(AlgoVersionData::getAlgoId, 
                        Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparing(AlgoVersionData::getCreatedAt)), Optional::get)));
  • 13
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值