文章目录

  • Stream特点
  • Stream语法
  • Stream创建
  • Stream和paralleStream区别
  • Stream对象方法使用
  • 中间操作
  • filter(过滤)
  • distinct(去重)
  • sorted(排序)
  • map(映射)重点
  • flatMap(映射)重点
  • 结束操作
  • forEach(遍历)
  • reduce(归约)
  • collect(搜集)
  • 通过Stream对List,Map操作和互转
  • Map转List
  • List转Map
  • 案例
  • List集合Map类型排序
  • Map值求和
  • Map 排序
  • Map 遍历倒序输出
  • peek 和 map 的区别
  • 使用 Collectors.toMap 将 List 转 Map
  • List<Map<String,Object>> stream 常用操作


Stream特点

  • 代码简洁: 函数式编程写出的代码简洁且意图明确,使用stream接口让你从此告别for循环。
  • 多核友好:Java函数式编程使得编写并行程序从未如此简单,你需要的全部就是调用一下parallel()方法。
  • 无存储:stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。
  • 为函数式编程而生:对stream的任何修改都不会修改背后的数据源,比如对stream执行过滤操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新stream。
  • 惰式执行:stream上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行。
  • 可消费性:stream只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器那样,想要再次遍历必须重新生成。

使用Stream需要三个操作步骤:创建Stream、中间操作以及结束操作

注意:因为stream操作是延迟的,这就意味着它们会等到需要结果的时候才执行,只有有终止操作,流的相关操作才会执行。

Stream语法

Java 流式数据处理 java流式编程_System

Stream创建

stream并不是某种数据结构,它只是数据源的一种视图。这里的数据源可以是一个数组,Java容器或I/O channel等。正因如此要得到一个stream通常不会手动创建,而是调用对应的工具方法。

1、 调用Collection系列集合的串行流:stream()方法或者并行流: parallelStream()方法

List<String> list = Arrays.asList("tom", "James");
// 获取并行的Stream对象
Stream<String> stream = list.parallelStream();
// 获取串行的Stream对象
Stream<String> stream = list.stream();

2、调用Arrays.stream(T[] array)方法获取数组流

String[] array = {"aa", "bb", "cc"};
Stream<String> stream = Arrays.stream(array);

3、调用Stream.of()方法

Stream<String> stream = Stream.of("tom", "james");

4、其他方式
Stream.iterate()
Stream.generate()

Stream和paralleStream区别

Stream:串行单管流,按顺序打印结果。
paralleStream:并行多管流,并行打印结果,每次结果都不一致。

parallelStream执行效率要比传统的for循环和stream要快的多。

时候要用stream或者parallelStream呢?

  • 是否需要并行?
  • 任务之间是否是独立的?是否会引起任何竞态条件?
  • 结果是否取决于任务的调用顺序?

Stream对象方法使用

Stream对象提供的方法分成两类:

  • 中间操作:将原始的Stream转换成另外一个Stream,并且总是会惰式执行,如filter返回的是过滤后的Stream。
  • 终端操作:产生的是一个结果或者其它的复合操作,如count或者forEach操作。

中间操作

方法

说明

sequential

返回一个相等的串行的Stream对象,如果原Stream对象已经是串行就可能会返回原对象

parallel

返回一个相等的并行的Stream对象,如果原Stream对象已经是并行的就会返回原对象

unordered

返回一个不关心顺序的Stream对象,如果原对象已经是这类型的对象就会返回原对象

onClose

返回一个相等的Steam对象,同时新的Stream对象在执行Close方法时会调用传入的Runnable对象

close

关闭Stream对象

filter

元素过滤:对Stream对象按指定的Predicate进行过滤,返回的Stream对象中仅包含未被过滤的元素

map

元素一对一转换:使用传入的Function对象对Stream中的所有元素进行处理,返回的Stream对象中的元素为原元素处理后的结果

mapToInt

元素一对一转换:将原Stream中的使用传入的IntFunction加工后返回一个IntStream对象

flatMap

元素一对多转换:对原Stream中的所有元素进行操作,每个元素会有一个或者多个结果,然后将返回的所有元素组合成一个统一的Stream并返回;

distinct

去重:返回一个去重后的Stream对象

sorted

排序:返回排序后的Stream对象

peek

使用传入的Consumer对象对所有元素进行消费后,返回一个新的包含所有原来元素的Stream对象

limit

获取有限个元素组成新的Stream对象返回

skip

抛弃前指定个元素后使用剩下的元素组成新的Stream返回

takeWhile

如果Stream是有序的(Ordered),那么返回最长命中序列(符合传入的Predicate的最长命中序列)组成的Stream;如果是无序的,那么返回的是所有符合传入的Predicate的元素序列组成的Stream。

dropWhile

与takeWhile相反,如果是有序的,返回除最长命中序列外的所有元素组成的Stream;如果是无序的,返回所有未命中的元素组成的Stream。

Java 流式数据处理 java流式编程_List_02

区分中间操作和结束操作最简单的方法,就是看方法的返回值,返回值为stream的大都是中间操作,否则是结束操作。

filter(过滤)


Java 流式数据处理 java流式编程_java_03

用于对Stream中的元素进行过滤,作用是返回一个只包含满足predicate条件元素的Stream。
函数原型:

Stream<T> filter(Predicate<? super T> predicate);

使用示例:

Stream<String> stream = Stream.of("I", "love", "you", "too");
// 保留长度等于3的字符串
stream = stream.filter(str -> str.length()==3);

由于filter()是个中间操作,如果只调用filter()不会有实际计算,因此也不会输出任何信息。

从List中过滤出一个元素:

User match = users.stream().filter((user) -> user.getId() == 1).findAny().get();

distinct(去重)


Java 流式数据处理 java流式编程_java_04

作用是返回一个去除重复元素之后的Stream。
函数原型:

Stream<T> distinct();

使用示例:

Stream<String> stream = Stream.of("I", "you", "too", "too");
// 去掉一个too字符串
stream = stream.distinct();

sorted(排序)

返回的是排序好后的Stream。
如果不指定一个自定义的Comparator则会使用默认排序。
一、实现Comparable接口,重写CompareTo()方法

// 默认排序
List<String> list = Arrays.asList("June", "Kmde", "Kang", "Zhan", "Gui");
Stream<String> stream = list.stream();
stream.sorted().forEach(System.out::println);

// 自定义排序
List<User> list = Arrays.asList(new User(23), new User(56));
// 按自然序排序
list.stream().sorted(Comparator.comparing(User::getAge));
// 按自然序逆序
list.stream().sorted(Comparator.comparing(User::getAge).reversed());

// 自定义复杂排序
List<User> list = Arrays.asList(new User(), new User());
list.stream().sorted((x, y)->{
    if(null == x.getAge()) {
        return 1;
    }
    if(null == y.getAge()) {
        return -1;
    }
    return y.getAge().compareTo(x.getAge());
});

map(映射)重点


Java 流式数据处理 java流式编程_Java 流式数据处理_05

元素一对一转换。
它接收一个Funcation参数,用其对Stream中的所有元素进行处理,返回的Stream对象中的元素为Function对原元素处理后的结果 。
直观的说,就是对每个元素按照某种操作进行转换,转换前后Stream中元素的个数不会改变。
函数原型:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

使用示例:

Stream<String> stream = Stream.of("I", "love", "you", "too");
// 将所有字符串转化成大写
stream = stream.map(str -> str.toUpperCase());
stream.forEach(System.out::println);

结果:

I
LOVE
YOU
TOO

从结果看出,流中的每个元素都应用了map()里的参数中的Function函数,并返回经过Function处理的元素。

flatMap(映射)重点


Java 流式数据处理 java流式编程_User_06

元素一对多转换。
它对原Stream中的所有元素使用传入的Function进行处理,每个元素经过处理后生成一个多个元素的Stream对象,然后将返回的所有Stream对象中的所有元素组合成一个统一的Stream并返回。转换前后元素的个数和类型都可能会改变。

函数原型:

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

使用示例:

Stream<List<Integer>> stream = Stream.of(Arrays.asList(1,2), Arrays.asList(3, 4, 5));
// 原来的stream中有两个元素,分别是两个List<Integer>,执行flatMap()之后,将每个List都“摊平”成了一个个的数字,所以会新产生一个由5个数字组成的Stream
stream = stream.flatMap(list -> list.stream());

Stream<String> stream = Stream.of("tom", "James");
// 将每一个String元素拆分成单个字母的Stream
stream = stream.flatMap(n -> Stream.of(n.split("")));

map、flatMap区别:

  • map一对一转化,将一个流中的所有元素按照逻辑执行后并返回。
  • flatMap一对一或者一对多,将一个流中的所有元素都转换成另一个流,然后把所有的流连接起来成为一个流。

结束操作

方法

说明

iterator

返回Stream中所有对象的迭代器;

spliterator

返回对所有对象进行的spliterator对象

forEach

对所有元素进行迭代处理,无返回值

forEachOrdered

按Stream的Encounter所决定的序列进行迭代处理,无返回值

toArray

返回所有元素的数组

reduce

使用一个初始化的值,与Stream中的元素一一做传入的二合运算后返回最终的值。每与一个元素做运算后的结果,再与下一个元素做运算。它不保证会按序列执行整个过程。

collect

根据传入参数做相关汇聚计算

min

返回所有元素中最小值的Optional对象;如果Stream中无任何元素,那么返回的Optional对象为Empty

max

与Min相反

count

所有元素个数

anyMatch

只要其中有一个元素满足传入的Predicate时返回True,否则返回False

allMatch

所有元素均满足传入的Predicate时返回True,否则False

noneMatch

所有元素均不满足传入的Predicate时返回True,否则False

findFirst

返回第一个元素的Optioanl对象;如果无元素返回的是空的Optional; 如果Stream是无序的,那么任何元素都可能被返回。

findAny

返回任意一个元素的Optional对象,如果无元素返回的是空的Optioanl。

isParallel

判断是否当前Stream对象是并行的

forEach(遍历)

作用是对容器中的每个元素执行action指定的动作,也就是对元素进行遍历。
函数原型:

void forEach(Consumer<? super E> action)

使用示例:

// 使用Stream.forEach()迭代
Stream<String> stream = Stream.of("I", "love", "you", "too");
stream.forEach(System.out::println);

运行结果:

Java 流式数据处理 java流式编程_System_07


由于forEach()是结束方法,上述代码会立即执行,输出所有字符串。

reduce(归约)

规约操作(reduction operation)又被称作折叠操作(fold),是通过某个连接动作将所有元素汇总成一个汇总结果的过程。元素求和、求最大值或最小值、求出元素总个数、将所有元素转换成一个列表或集合,都属于规约操作。
Stream类库有两个通用的规约操作reduce()collect(),也有一些为简化书写而设计的专用规约操作,比如sum()、max()、min()、count()等。
最大或最小值这类规约操作很好理解(至少方法语义上是这样),我们着重介绍reduce()collect(),这是比较有魔法的地方。
使用示例:

@Test
 public void test() {
     List<Integer> list = Arrays.asList(1, 2, 3, 4);
     // x:上一次执行的返回值,默认0;y:当前遍历的值
     Integer sum = list.stream().reduce(0, (x, y) -> {
         System.out.println("上次的返回值:" + x);
         System.out.println("这一次遍历的值:" + y);
         return x + y;
     });
     System.out.println("总和:" + sum);
     System.out.println("---黄金分割线---");

     List<String> list2 = Arrays.asList("June", "Kmde", "Kang", "Zhan", "Gui");
     Optional<String> result = list2.stream().reduce((x, y) -> x + "_" + y);
     System.out.println(result.get());
 }

运行结果:

Java 流式数据处理 java流式编程_List_08

collect(搜集)

接收的参数是将流中的元素累积到汇总结果的各种方式(称为收集器)。

可以转化成List、Set、Map …

// 把流中所有元素收集到List中
List<String> names = stream1.map(Student::getName).collect(Collectors.toList());
// 把流中所有元素收集到Set中,删除重复项
Set<String> names = stream1.map(Student::getName).collect(Collectors.toSet());
// 把流中所有元素收集到给定的供应源创建的集合中
Set<String> names = stream1.map(Student::getName).collect(Collectors.toCollection(HashSet::new));
// 把流中所有元素收集到Map中
Map<Integer, String> names = stream1.distinct().collect(Collectors.toMap(Student::getId, Student::getName));

//将集合中所有的名字连接在一起,并逗号分割
String allNameStr1 = stuList.stream().map(Stu::getName).collect(Collectors.joining(","));

通过Stream对List,Map操作和互转

Map转List

List<User> list1 = map.entrySet().stream()
        .sorted(Comparator.comparing(e -> e.getKey()))
        .map(e -> new User(e.getKey()))
        .collect(Collectors.toList());

List<User> list2 = map.entrySet().stream()
        .sorted(Comparator.comparing(Map.Entry::getValue))
        .map(e -> new User(e.getKey()))
        .collect(Collectors.toList());

List<User> list3 = map.entrySet().stream()
        .sorted(Map.Entry.comparingByKey())
        .map(e -> new User(e.getKey()))
        .collect(Collectors.toList());

List<User> list = map.entrySet().stream()
        .sorted(Map.Entry.comparingByValue())
        .map(e -> new User(e.getKey()))
        .collect(Collectors.toList());

以上方式不同之处在于排序的处理。

List转Map

List<User> list = Arrays.asList(new User("10", "James"), new User("20", "James"));
Map<String, String> collect = list.stream().collect(Collectors.toMap(User::getAge, User::getName));
collect.forEach((key, value)->{
    System.out.println(key);
    System.out.println(value);
});

案例

List集合Map类型排序

// Map排序
List<Map<String, String>> lists = new ArrayList<>();
Map<String, String > map1 = new HashMap<>();
map1.put("age", "15");
map1.put("name", "James");
Map<String, String > map2 = new HashMap<>();
map2.put("age", "20");
map2.put("name", "Tom");
lists.add(map1);
lists.add(map2);
lists.stream().sorted(Comparator.comparing(Map::entrySet, (x, y)->{
    List<Map.Entry<String, String>> xList = x.stream().filter(xFilter -> xFilter.getKey().equalsIgnoreCase("age")).collect(Collectors.toList());
    List<Map.Entry<String, String>> yList = y.stream().filter(yFilter -> yFilter.getKey().equalsIgnoreCase("age")).collect(Collectors.toList());
    return xList.get(0).getValue().compareTo(yList.get(0).getValue());
}));

Optional<Integer> todayTargetCount = ProvinceCodeMap.qrcodepurcharseWebsiteProvinceSource2020TargetMap.entrySet().stream().map(map -> map.getValue())

Map值求和

Map<Integer, Integer> map = new HashMap<>();
map.put(101,   448800);
map.put(1775,  241200);
map.put(2732,  325200);
Optional<Integer> total = map.entrySet().stream()
        .map(mapTmp -> mapTmp.getValue()).collect(Collectors.toList())
        .stream().reduce((x, y) -> x + y);
System.out.println(total.get());

Map 排序

实际需求,针对 list 根据数据时间分组,分组后的 map 无序,但需要按照时间正序排列。

// 先根据时间分组
Map<Object, List<HashMap<String, Object>>> listMap = cardRecord.stream().collect(Collectors.groupingBy(item -> item.getOrDefault("data", "")));

// 分组后根据key正序排列,()LinkedHashMap有序)
listMap = listMap.entrySet().stream().sorted(new Comparator<Map.Entry<Object, List<HashMap<String, Object>>>>() {
    @Override
    public int compare(Map.Entry<Object, List<HashMap<String, Object>>> o1, Map.Entry<Object, List<HashMap<String, Object>>> o2) {
        try {
            Date d1 = DateUtil.parse(o1.getKey().toString(), "yyyy-MM-dd");
            Date d2 = DateUtil.parse(o2.getKey().toString(), "yyyy-MM-dd");
            return d2.compareTo(d1);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }
}).collect(Collectors.toMap(
        Map.Entry::getKey,
        Map.Entry::getValue,
        (oldVal, newVal) -> oldVal,
        LinkedHashMap::new
));

Map 遍历倒序输出

public static void main(String[] args) {
    Map<String,String>  map = new HashMap<String,String>();
    map .put("a", "1");
    map .put("b", "2");
    map .put("c", "3");
    ListIterator<Map.Entry<String, String>> li = new ArrayList<Map.Entry<String, String>>(map.entrySet()).listIterator(map.size());
    
    while(li.hasPrevious()) {  
        Map.Entry<String, String> entry = li.previous();  
        System.out.println(entry.getKey()+":"+entry.getValue());  
    }
}

peek 和 map 的区别

peek用来修改数据
map用来转换数据类型

使用 Collectors.toMap 将 List 转 Map

userList.stream().collect(Collectors.toMap(User::getId, User::getName));

当然,如果希望得到 Map的value为对象本身时,可以这样写:
userList.stream().collect(Collectors.toMap(User::getId, t -> t));
或者:
userList.stream().collect(Collectors.toMap(User::getId, Function.identity()));

Collectors.toMap有三个重载方法:

toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper);
toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper,
        BinaryOperator<U> mergeFunction);
toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper,
        BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier);

参数解释:

  1. keyMapper:Key 的映射函数
  2. valueMapper:Value的映射函数
  3. mergeFunction:当 Key 冲突时,调用的合并方法
  4. mapSupplier:Map 构造器,在需要返回特定的 Map 时使用

【强制】在使用java.util.stream.Collectors 类的 toMap()方法转为 Map 集合时,一定要使用含有参数类型为BinaryOperator,参数名为 mergeFunction 的方法,否则当出现相同key值时会抛出 IllegalStateException 异常。
说明:参数 mergeFunction 的作用是当出现 key 重复时,自定义对value 的处理策略。

List<User> userList = new ArrayList<>();
userList.add(new User().setId("A").setName("张三"));
userList.add(new User().setId("A").setName("李四"));//相同的key
userList.add(new User().setId("C").setName("王五"));
userList.stream().collect(Collectors.toMap(User::getId, User::getName));

// 异常:
java.lang.IllegalStateException: Duplicate key 张三 
    at java.util.stream.Collectors.lambda$throwingMerger$114(Collectors.java:133)
    at java.util.HashMap.merge(HashMap.java:1245)
    at java.util.stream.Collectors.lambda$toMap$172(Collectors.java:1320)
    at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
    at Test.toMap(Test.java:17)
    ...

解决方案:

userList.stream().collect(Collectors.toMap(User::getId, User::getName, (n1, n2) -> n1 + n2));

// 输出结果:
A-> 张三李四 
C-> 王五 

转化为ConcurrentMap:
userList.stream().collect(
    Collectors.toMap(User::getId, User::getName, (n1, n2) -> n1, ConcurrentHashMap::new)
);

List<Map<String,Object>> stream 常用操作

1、测试集合

List<Map<String,Object>> listMap = new ArrayList<>();
Map<String,Object> temp = new HashMap<>();
temp.put("name","张三");
temp.put("age",20);
temp.put("height",new BigDecimal("170.32"));
listMap.add(temp);
temp = new HashMap<>();
temp.put("name","李四");
temp.put("age",20);
temp.put("height",new BigDecimal("185.32"));
listMap.add(temp);
temp = new HashMap<>();
temp.put("name","王五");
temp.put("age",21);
temp.put("height",new BigDecimal("192.32"));
listMap.add(temp);

BigDecimal 求和:

BigDecimal sum = listMap.stream().map(e -> (BigDecimal)e.get("height")).reduce(BigDecimal.ZERO, BigDecimal::add);

BigDecimal 最大值、最小值:

Map<String, Object> mapMax = listMap.stream().max((v1, v2) -> ((BigDecimal) v1.get("height")).compareTo((BigDecimal) v2.get("height"))).orElse(null);
Map<String, Object> mapMin = listMap.stream().min((v1, v2) -> ((BigDecimal) v1.get("height")).compareTo((BigDecimal) v2.get("height"))).orElse(null);

List<Map<String,Object>> 分组、排序、筛选

Map<String, List<Map<String, Object>>> mapGroup = listMap.stream().collect(Collectors.groupingBy(e -> e.get("name").toString()));

List<Map<String,Object>> mapSort = listMap.stream().sorted(Comparator.comparing(map->(BigDecimal) map.get("height"))).collect(Collectors.toList()); // 正序
Collections.reverse(mapSort); // 倒序

// 筛选 全部
List<Map<String,Object>> mapFindList = listMap.stream().filter(e -> "20".equals(e.get("age").toString())).collect(Collectors.toList());
// 查找一个
Map<String,Object> mapFindOne = listMap.stream().filter(p -> "20".equals(p.get("age").toString())).findAny().orElse(null);

其他操作:

// 求和
int ageAll = listMap.stream().mapToInt(e -> (int) e.get("age")).sum();
// 最大值
int ageMax = listMap.stream().mapToInt(e -> (int) e.get("age")).summaryStatistics().getMax();
// 最小值
int ageMin = listMap.stream().mapToInt(e -> (int) e.get("age")).summaryStatistics().getMin();
// 均值
double ageAvg = listMap.stream().mapToInt(e -> (int) e.get("age")).summaryStatistics().getAverage();
// 获取总数
long age = listMap.stream().mapToInt(e -> (int) e.get("age")).summaryStatistics().getCount();