JAVA流
一、流的创建
1.由值创建流
//由值创建 Stream<Integer> integerStream = Stream.of(1, 2, 3); Stream.empty(); //流构造器创建 Stream.<Integer>builder().add(1).add(2).add(3).build();
2.数组、列表、文件创建
//由数组创建流 Stream<String> arrayStream = Arrays.stream(strs); //由列表创建流 Stream<String> listStream = list.stream(); //由文件创建 Stream<String> fileStream = Files.lines(Path.of("D:\\1.txt"));
二、流的操作
流操作注意点:
(1) 流操作不更改原始集合, 会生成一个新的集合 (2) 流操作是懒惰执行的, 直到需要拿到执行结果的时候才会执行
Stream不调用终止方法,中间的操作不会执行。
1.中间操作
Filter
返回与指定谓词匹配的流。
// 筛选重量大于120g的水果 List<Fruits> list = getList(); list.stream().filter(s -> s.getWeight() > 120);
Distinct
通过检查equals()方法返回由不同元素组成的流。
// 对这个集合中的元素去重 List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4); numbers.stream().distinct();
Limit
按数字截断流。
// 筛选重量大于120g的水果,选取前三个 List<Fruits> list = getList(); list.stream().filter(s -> s.getWeight() > 120).limit(3);
Skip
丢弃前n个元素并返回剩余流。如果此流包含少于请求的流,则返回空流。
// 筛选重量大于120g的水果,跳过前三个,返回剩下的 List<Fruits> list = getList(); list.stream().filter(s -> s.getWeight() > 120).skip(3);
Map
对流执行一对一映射
map方法接受一个Function<T, R>类型的函数作为参数,流中的元素T经过这个流会映射为一个新元素R。
简而言之:将流中的元素转换为另外一个流中的元素
// 获取流中每个元素的长度 List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action"); words.stream().map(String::length);
flatMap
生成流扁平化。
flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流
java中map是把集合每个元素重新映射,元素个数不变,但是元素值发生了变化。而flatMap从字面上来说是压平这个映射,实际作用就是将每个元素进行一个一对多的拆分,细分成更小的单元,返回一个新的Stream流,新的流元素个数增加。
如下示例:使用flatMap将本来的数组流变成一个流。
List<String> wordList1 = Arrays.asList("Hello", "World"); List<String> wordList2 = Arrays.asList("Java", "Stream"); List<List<String>> listOfWordLists = Arrays.asList(wordList1, wordList2); List<String> flattenedList = listOfWordLists.stream() .flatMap(List::stream) .collect(Collectors.toList()); System.out.println(flattenedList);
Sorted
根据自然顺序或指定的比较器对流进行排序。对于有序流,排序是稳定的。
sorted() 默认使用自然序排序, 其中的元素必须实现Comparable 接口。
sorted(Comparator<? super T> comparator) :我们可以使用lambada 来创建一个Comparator 实例。可以按照升序或着降序来排序元素。
官网示例:
#自然序排序一个list list.stream().sorted() #自然序逆序元素,使用Comparator 提供的reverseOrder() 方法 list.stream().sorted(Comparator.reverseOrder()) # 使用Comparator 来排序一个list list.stream().sorted(Comparator.comparing(Account::getId)) # 颠倒使用Comparator 来排序一个list的顺序,使用Comparator 提供的reverseOrder() 方法 list.stream().sorted(Comparator.comparing(Account::getId).reversed()) #联合排序 先按照ID排序再按照年龄排序 list.stream().sorted(Comparator.comparing(Account::getId).thenComparing(Comparator.comparing(Account::getAge)))
2.终端操作
anyMatch
如果流中的任何元素与指定的谓词匹配,则返回true,否则返回false。如果流为空,则返回false。
// 判断流中是否存在苹果 List<Fruits> list = getList(); boolean b = list.stream().anyMatch(s -> "苹果".equals(s.getName()));
allMatch
如果流中的所有元素都匹配指定的谓词,则返回true,否则返回false。如果流为空,则返回true。
// 判断流中元素重量是否都大于10g List<Fruits> list = getList(); boolean b = list.stream().allMatch(s -> s.getWeight() > 10);
noneMatch
如果流中没有元素匹配指定的谓词,则返回true,否则返回false。如果流为空,则返回true。
// 判断流中元素重量是否都不大于(小于)10g List<Fruits> list = getList(); boolean b = list.stream().noneMatch(s -> s.getWeight() > 10);
findAny
返回流中的任何元素。返回空流的一个空的Optional对象,找到一个元素即立即结束。
// 找到重量大于100g的任意一元素 List<Fruits> list = getList(); Optional<Fruits> any = list.stream().filter(s -> s.getWeight() > 100).findAny();
findFirst
返回流的第一个元素。对于有序流,它返回第一个元素;对于无序流,它返回任何元素。
// 找到重量大于100g的第一个元素 List<Fruits> list = getList(); Optional<Fruits> any = list.stream().filter(s -> s.getWeight() > 100).findFirst();
findAny是任意一个元素,fingFirst是返回第一个元素,这里需要的流为顺序流(如List或者其他排好序的流)。
Reduce
应用缩减操作以从流计算单个值。reduce 将多个对象转化一个对象。例如求和,求积,求最大值等等操作。
reduce是流操作比较核心的一个方法,max,min,collector等操作,基本都是由reduce来实现的。
// 对所有元素的重量求和 List<Fruits> list = getList(); Optional<Integer> reduce = list.stream().map(Fruits::getWeight).reduce( Integer::sum); // 得到所有元素中最大的重量的值 List<Fruits> list = getList(); Optional<Integer> reduce = list.stream().map(Fruits::getWeight).reduce( Integer::max); // 得到所有元素中最小的重量的值 List<Fruits> list = getList(); Optional<Integer> reduce = list.stream().map(Fruits::getWeight).reduce( Integer::min);
reduce方法可以赋予初始值,reduce方法第一个参数是默认值,可以不填,不填的话会返回一个Optional对象,考虑流中没有任何元素的情况下reduce方法不能求和,所以将结果包含在Optional对象中。
//未简写的求和方法 //代码实现了对list 中的元素累加。lambada表达式的a参数是表达式的执行结果的缓存,也就是表达式这一次的执行结果会被作为下一次执行的参数,而第二个参数b则是依次为stream中每个元素。如果表达式是第一次被执行,a则是stream中的第一个元素。 List<Integer> numbers = Arrays.asList(1, 2, 3, 4); int sum = numbers.stream().reduce((a, b) -> a + b).get(); //填写初始默认值 //与第一个的实现的唯一区别是它首次执行时表达式第一次参数并不是stream的第一个元素,而是通过签名的第一个参数identity来指定。我们来通过这个签名对之前的求和代码进行改进。 List<Integer> list = Arrays.asList(1,2,3,4); int result = list.stream().reduce(0, (a,b) -> a+b); //其实这两种实现几乎差别,第一种比第一种仅仅多了一个字定义初始值罢了。 此外,因为存在stream为空的情况,所以第一种实现并不直接方法计算的结果,而是将计算结果用Optional来包装,我们可以通过它的get方法获得一个Integer类型的结果,而Integer允许null。第二种实现因为允许指定初始值,因此即使stream为空,也不会出现返回结果为null的情况,当stream为空,reduce为直接把初始值返回。
第三种用法相较前两种稍显复杂,由于前两种实现有一个缺陷,它们的计算结果必须和stream中的元素类型相同,如上面的代码示例,stream中的类型为int,那么计算结果也必须为int,这导致了灵活性的不足,甚至无法完成某些任务, 比入我们要对一个一系列int值求和,但是求和的结果用一个int类型已经放不下,必须升级为long类型,此实第三种就能发挥价值了,它不将执行结果与stream中元素的类型绑死。
List<Integer> list = Arrays.asList(Integer.MAX_VALUE,Integer.MAX_VALUE); long result = list .stream().reduce(0L,(a,b) -> a + b, (a,b)-> null);
collect
collect和reduce一样都是一个归约操作。可以接受一个参数将流中的元素汇总为一个结果。
1.可以收集流中的数据到【集合】或者【数组】中去。
//1.收集数据到list集合中 stream.collect(Collectors.toList()) //2.收集数据到set集合中 stream.collect(Collectors.toSet()) //3.收集数据到指定的集合中 Collectors.toCollection(Supplier<C> collectionFactory) stream.collect(Collectors.joining())
数据聚合、分组、分区、拼接操作
除了 collect() 方法将数据收集到集合/数组中。对 Stream流 的收集还有其他的方法。比如说:聚合计算,分组,多级分组,分区,拼接等。
//最大值 Collectors.maxBy(); //最小值 Collectors.minBy(); //总和 Collectors.summingInt();/Collectors.summingDouble();/Collectors.summingLong(); //平均值 Collectors.averagingInt();/Collectors.averagingDouble();/Collectors.averagingLong(); //总个数 Collectors.counting(); //示例,返回水果重量总和 // 如果流为空则返回0,默认值为0 Integer collect = list.stream().collect(Collectors.summingInt(Fruits::getWeight)); // 获取最重的水果 List<Fruits> list = getList(); Comparator<Fruits> fruitsComparator = Comparator.comparingInt(Fruits::getWeight); Optional<Fruits> maxFruits = list.stream().collect(Collectors.maxBy(fruitsComparator)); //获取最轻的水果 List<Fruits> list = getList(); Comparator<Fruits> fruitsComparator = Comparator.comparingInt(Fruits::getWeight); Optional<Fruits> maxFruits = list.stream().collect(Collectors.minBy(fruitsComparator));
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());
joining工厂方法返回的收集器会把对流中每一个对象应用toString方法得到的所有字符串连接成一个字符串。
String collect = list.stream().map(Fruits::getName).collect(Collectors.joining());
该字符串的可读性并不好。joining工厂方法有一个重载版本可以接受元素之间的分界符
String collect = list.stream().map(Fruits::getName).collect(Collectors.joining(","));
分组
// 将不同名称的元素分为不同的组 Map<String, List<Fruits>> collect = list.stream().collect(Collectors.groupingBy(Fruits::getName));
也可以根据重量来分组,小于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";} }));
多级分组
多级分组就是可以先按名称分组再按重量分组,可以利用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";} })));
第二个参数其实可以不是Collectors.groupingBy方法的,也可以是Collectors.counting方法来收集数量。
Map<String, Long> collect = list.stream().collect(Collectors.groupingBy(Fruits::getName, Collectors.counting()));
还可以获取不同元素中最大的元素是哪个
Map<String, Optional<Fruits>> collect = list.stream().collect(Collectors.groupingBy(Fruits::getName, Collectors.maxBy(Comparator.comparingInt(Fruits::getWeight))));
使用collect来进行分区
分区就是分为2个区,一个true一个false,例如按重量分,小于100g的为一个区,其他的为另一个区。
Map<Boolean, List<Fruits>> collect = list.stream().collect(Collectors.partitioningBy(fruits -> fruits.getWeight() < 100));
上面返回的集合key分别为true和false。
将分区和分组结合起来使用,先将果篮分为小于100g的一个区和其他区,再按名字分组。
Map<Boolean, Map<String, List<Fruits>>> collect = list.stream().collect(Collectors.partitioningBy(fruits -> fruits.getWeight() < 100, Collectors.groupingBy(Fruits::getName)));
list转map
Collectors.toMap方法
1.两个参数的转换方法
public class ToMapTest { @Data @Accessors(chain = true) private static class User { private String id; private String name; } public static void main(String[] args) { List<User> list = Arrays.asList( new User().setId("1").setName("张三"), new User().setId("2").setName("李四"), new User().setId("3").setName("王五") ); //2个参数的toMap方法 Map<String, String> map = list.stream() .collect(Collectors.toMap(User::getId, User::getName)); System.out.println(map); } }
上述方法需要key也就是User的Id不重复,如果Key重复则会抛异常。
2.三个参数的转换方法
三个参数的转换方法可以避免抛异常,会对重复的key的value进行处理
//3个参数的toMap方法 Map<String, String> map = list.stream().collect( Collectors.toMap( User::getId, User::getName, //v1、v2表示两个重复key的value,下面是对value的处理,可根据自己业务需求来 (v1, v2) -> v1 + "、" + v2 ) );
这里还需要注意一点:
HashMap对象的key、value值均可为null。
但是,toMap的话,value为null,也是会抛异常-空指针异常!
3.四个参数
第四个参数用于自定义返回 Map 类型,比如我们希望返回的 Map 是根据 Key 排序:
//4个参数的toMap方法 Map<String, String> map = list.stream().collect( Collectors.toMap( User::getId, u->u.getName() == null? "未知" : u.getName(), (v1, v2) -> v1 + "、" + v2, TreeMap::new ) );
Function.identity()
Function.identity()
返回一个输出跟输入一样的Lambda表达式对象,等价于形如t -> t
形式的Lambda表达式。
所以如果需要输出跟输入一样的对象时可以直接使用Function.identity();
例如上述代码转为map时value和输出对象一样时可以这样写:
Map<String, User> map = list.stream() .collect(Collectors.toMap(User::getId,Function.identity()));
这样输出的就是key为Id,value为User对象的一个map。