公子奇带你进入Java8流的世界(二)
在上一篇中我们带领大家简单的了解流的概念及使用场景,知道了流的本质操作是将外部迭代转为了内部迭代,由此程序并发性上得到了很大的优化。不过我们在面对复杂的需求是又如何通过流来操作呢?在本节我们就来好好的介绍流的常见用法,以此来处理复杂数据,以及流的其他类型:数值流、文件流以及无限流等内容。
一、筛选和切片
对于一串流,我们有时需要取出我们需要的流中某些元素,例如:符合某些条件的元素、只需要流中的某一段等。主要是通过谓词筛选。
1、我们如何去除流中重复的元素,以及什么样的元素是重复元素呢?流中提供了一个distinct方法来实现。
2、如何取出流中某一段长度的流,流中提供了一个limit方法来满足我们的需求。
3、若我们需要跳过流中的某一段,又该如何做呢?同样Java8中也提供了一个skip方法来满足我们跳过固定长度的流。
下面我们带着这三个疑问及提示用代码来说明问题。
首先定义一个POJO,后续操作都基于此来实例化元素。
1 package com.hz; 2 3 /** 4 * 民警实体类 5 */ 6 public class Police { 7 /** 8 * 民警警号 9 */ 10 private String policeNo; 11 /** 12 * 民警姓名 13 */ 14 private String policeName; 15 /** 16 * 民警年龄 17 */ 18 private Integer policeAge; 19 /** 20 * 是否退休 21 */ 22 private boolean policeIsRetire; 23 24 public Police(String policeNo, String policeName, Integer policeAge, boolean policeIsRetire) { 25 this.policeNo = policeNo; 26 this.policeName = policeName; 27 this.policeAge = policeAge; 28 this.policeIsRetire = policeIsRetire; 29 } 30 31 public String getPoliceNo() { 32 return policeNo; 33 } 34 35 public void setPoliceNo(String policeNo) { 36 this.policeNo = policeNo; 37 } 38 39 public String getPoliceName() { 40 return policeName; 41 } 42 43 public void setPoliceName(String policeName) { 44 this.policeName = policeName; 45 } 46 47 public Integer getPoliceAge() { 48 return policeAge; 49 } 50 51 public void setPoliceAge(Integer policeAge) { 52 this.policeAge = policeAge; 53 } 54 55 public boolean isPoliceIsRetire() { 56 return policeIsRetire; 57 } 58 59 public void setPoliceIsRetire(boolean policeIsRetire) { 60 this.policeIsRetire = policeIsRetire; 61 } 62 63 @Override 64 public String toString() { 65 return "Police{" + 66 "policeNo='" + policeNo + '\'' + 67 ", policeName='" + policeName + '\'' + 68 ", policeAge=" + policeAge + 69 ", policeIsRetire='" + policeIsRetire + '\'' + 70 '}'; 71 } 72 73 @Override 74 public boolean equals(Object o) { 75 if (this == o) return true; 76 if (!(o instanceof Police)) return false; 77 78 Police police = (Police) o; 79 80 if (policeIsRetire != police.policeIsRetire) return false; 81 if (!policeNo.equals(police.policeNo)) return false; 82 if (!policeName.equals(police.policeName)) return false; 83 return policeAge.equals(police.policeAge); 84 } 85 86 @Override 87 public int hashCode() { 88 int result = policeNo.hashCode(); 89 result = 31 * result + policeName.hashCode(); 90 result = 31 * result + policeAge.hashCode(); 91 result = 31 * result + (policeIsRetire ? 1 : 0); 92 return result; 93 } 94 }
1 package com.hz; 2 3 import java.util.Arrays; 4 import java.util.List; 5 6 import static java.util.stream.Collectors.toList; 7 8 /** 9 * 筛选某些元素 10 * 包含:filter / distinct / limit / skip 11 */ 12 public class FilterAndLimitStreamDemo { 13 public static void main(String[] args) { 14 List<Police> policeList = Arrays.asList( 15 new Police("P001", "余警官", 27, false), 16 new Police("P002", "李警官", 32, false), 17 new Police("P003", "程警官", 25, false), 18 new Police("P004", "杨警官", 35, false), 19 new Police("P005", "张警官", 70, true), 20 new Police("P006", "王警官", 68, true), 21 new Police("P007", "赵警官", 77, true), 22 new Police("P008", "刘警官", 64, true), 23 new Police("P008", "刘警官", 64, true) 24 ); 25 26 //***** 1-使用谓词筛选 27 List<Police> filterPolices = policeList.stream().filter(Police::isPoliceIsRetire).collect(toList()); 28 System.out.println("结果1: " + filterPolices); 29 30 System.out.println("---------------- 分割线 ---------------------"); 31 32 //***** 2-筛选 大于60岁 并 去除重复的数据(是否重复 根据HashCode和equal决定 两者同时) 33 policeList.stream().filter(p -> p.getPoliceAge() > 60).distinct().forEach(System.out :: println); 34 35 System.out.println("---------------- 分割线 ---------------------"); 36 37 //***** 3-截流 截取前三位退休的民警 38 policeList.stream().filter(Police :: isPoliceIsRetire).limit(3).forEach(System.out :: println); 39 40 System.out.println("---------------- 分割线 ---------------------"); 41 42 //***** 4-跳过几个元素 获取退休民警 并跳过前两位退休民警 43 policeList.stream().filter(Police :: isPoliceIsRetire).skip(2).forEach(System.out :: println); 44 } 45 }
二、映射
映射即将流中的元素进行处理后返回新的流,即从某些对象中选择信息。在SQL语言中,我们通常会取出表中的某个字段信息,将其返回给页面展示,若在Java语言中,如何实现同样的功能呢?语言也提供了两个方法让我们来实现。
1、map:该方法会对流中的每个元素进行处理,我们可以理解为将流中的每个元素进行转换后返回一个新的流。例如我们如何取出所有民警的姓名。
2、flatMap:既然有了map方法,Java又为什么提供一个flatMap呢?此为流的扁平化。我们之前讲解过,小的流和组成一个大的流,若我们有个大的流,大的流中有多少小的流我们并不知道,此时我们无法对每个小的流再次进行处理,而flatMap方法即可解决我们的问题,它可以将大流中的小流的值组成一个新的大流,之后再对该流进行元素处理。例如我们需要将多个单词重复的字母给去除,此时每个单词即可组装为一个小流,若我们使用map来处理,只会将每个单词自己的重复字母给去除,两个单词之间的重复字母无法去除。
1 package com.hz; 2 3 import java.util.Arrays; 4 import java.util.List; 5 import java.util.stream.Stream; 6 7 import static java.util.stream.Collectors.toList; 8 9 /** 10 * 流中映射 11 */ 12 public class StreamMapDemo { 13 public static void main(String[] args) { 14 List<Police> policeList = Arrays.asList( 15 new Police("P001", "余警官", 27, false), 16 new Police("P002", "李警官", 32, false), 17 new Police("P003", "程警官", 25, false), 18 new Police("P004", "杨警官", 35, false), 19 new Police("P005", "张警官", 70, true), 20 new Police("P006", "司马警官", 68, true), 21 new Police("P007", "赵科", 77, true), 22 new Police("P008", "刘警官", 64, true), 23 new Police("P008", "刘警官", 64, true) 24 ); 25 26 //***** 1-对流元素中的字段进行处理 获取民警的姓名 和 获取民警姓名长度 27 policeList.stream().map(Police :: getPoliceName).forEach(System.out :: println); 28 29 List<Integer> policesNameLength = policeList.stream().map(Police::getPoliceName).map(String::length).collect(toList()); 30 System.out.println("结果: " + policesNameLength); 31 32 System.out.println("------------ 分割线 ----------------"); 33 34 //***** 2-流的扁平化 将一组单词 重复的字母去除 35 String[] words = {"gong", "zi", "chuan", "qi"}; 36 Stream<String> wordsStream = Arrays.stream(words); //将数组转为流对象 37 Stream<String> wordsStream2 = Arrays.stream(words); //将数组转为流对象 38 39 //此时为 将每个单词为一个数组转为一个流即四个流 流本身没有重复 两个流之间是存在重复的 40 List<Stream<String>> streamsList = wordsStream.map(s -> s.split("")).map(Arrays::stream).distinct().collect(toList()); 41 System.out.println(streamsList); 42 //wordsStream为一个流 不可多次使用 43 // java8中有个扁平化的处理 为将流中的值组成一个大的流 flatMap 44 List<String> disWordsList = wordsStream2.map(s -> s.split("")).flatMap(Arrays::stream).distinct().collect(toList()); 45 System.out.println("结果:" + disWordsList); 46 47 System.out.println("---------------- 分割线 --------------------"); 48 49 //***** 3-一个实例 给定两个数字列表,如何返回所有的数对呢? 50 // 例如,给定列表[1, 2, 3]和列表[3, 4],应该返回[(1, 3), (1, 4), (2, 3), (2, 4), (3, 3), (3, 4)] 51 Integer[] ints1 = {1, 2, 3}; 52 Integer[] ints2 = {3, 4}; 53 54 // List<List<int[]>> result = Arrays.stream(ints1).map(i -> { 55 // List<int[]> tempList = Arrays.stream(ints2).map(j -> { 56 //// int[] temp = new int[2]; 57 //// temp[0] = i; 58 //// temp[1] = j; 59 //// return temp; 60 // return new int[]{i, j}; 61 // }).collect(toList()); 62 // return tempList; 63 // }).collect(toList()); 64 65 //更简化 66 List<List<int[]>> result = Arrays.stream(ints1).map(i -> Arrays.stream(ints2).map(j -> new int[]{i, j}).collect(toList())).collect(toList()); 67 68 result.forEach(l -> l.forEach(is -> System.out.println((is[0] + "," + is[1])))); 69 } 70 }
三、查找和匹配
有时我们需要查看数据集中的某些元素是否匹配一个给定的属性。对于该需求,Java中分别提供了 allMatch 、 anyMatch 、 noneMatch 、 findFirst 和 findAny 方法来满足我们。
1、allMatch:该方法表示流中的所有元素都满足我们的条件。例如我们想知道当前的民警容器中是不是所有的民警都为退休民警。
2、anyMatch:该方法表示流中的元素至少有一个满足我们的条件。例如我们想知道有没有民警退休了。
3、noneMatch:表示所有的元素都不满足。它与allMatch正好相反。
4、findFirst:在流中找出第一个元素,该方法我们一般会配合筛选来使用。
5、findAny:在流中随意找出一个元素,一般情况下方法返回给我们的都为第一个元素。
1 package com.hz; 2 3 import java.util.*; 4 5 /** 6 * 流中查找和匹配 7 */ 8 public class FindAndMatchDemo { 9 public static void main(String[] args) { 10 List<Police> policeList = Arrays.asList( 11 new Police("P001", "余警官", 27, false), 12 new Police("P002", "李警官", 32, false), 13 new Police("P003", "程警官", 25, false), 14 new Police("P004", "杨警官", 35, false), 15 new Police("P005", "张警官", 70, true), 16 new Police("P006", "司马警官", 68, true), 17 new Police("P007", "赵科", 77, true), 18 new Police("P008", "刘警官", 64, true), 19 new Police("P008", "刘警官", 64, true) 20 ); 21 22 //***** 1-检查流中元素至少匹配一个 民警列表中至少有一名退休民警 23 System.out.println("--------------- anyMatch分割线 ------------------"); 24 if (policeList.stream().anyMatch(Police :: isPoliceIsRetire)) { 25 System.out.println("列表存在退休民警..."); 26 } else { 27 System.out.println("当前没有退休民警..."); 28 } 29 30 System.out.println("----------- allMatch分割线 ------------"); 31 32 //***** 2-检查流中是否匹配所有元素 是否为退休民警列表 33 if (policeList.stream().allMatch(Police :: isPoliceIsRetire)) { 34 System.out.println("该列表为一个退休民警列表..."); 35 } else { 36 System.out.println("该列表中存在未退休的民警..."); 37 } 38 39 System.out.println("------------- noneMatch分割线 -----------"); 40 //与allMatch对应的是noneMatch 即所有都不匹配 41 if (policeList.stream().noneMatch(Police::isPoliceIsRetire)) { 42 System.out.println("该列表都不是退休民警"); 43 } else { 44 System.out.println("该列表存在退休民警"); 45 } 46 47 System.out.println("--------------- 查找分割线 --------------------"); 48 49 //***** 3-查找元素 从民警列表中找出任一元素 50 Optional<Police> anyPolice = policeList.stream().findAny(); 51 System.out.println(anyPolice); 52 //在这里Optional这是一个容器类,该容器中只能存储一个对象 其中Optional中实现了一些方法用来操作和判断该容器是否有值 53 //这里我们简单了解下,详细后面我们在好好介绍下该类 54 //***** 案例:找出任意一个退休民警 并打印该民警姓名 ifPresent若有值则执行 55 policeList.stream().filter(Police :: isPoliceIsRetire).findAny().ifPresent(p -> System.out.println(p.getPoliceName())); 56 boolean hasValue = policeList.stream().filter(Police::isPoliceIsRetire).findAny().isPresent(); //是否有值isPresent 57 System.out.println("容器中是否有匹配的值:" + hasValue); 58 // 若容器中有值则返回,否则返回一个默认的值 59 Police police = policeList.stream().filter(Police :: isPoliceIsRetire).findAny().orElse(new Police("","",0, false)); 60 System.out.println("返回民警: " + police); 61 //获取匹配的民警 有则返回 没有则异常java.util.NoSuchElementException: No value present 62 Police police1 = policeList.stream().filter(Police::isPoliceIsRetire).findAny().get(); 63 System.out.println("获取民警: " + police1); 64 65 System.out.println("---------------"); 66 //***** 4-查找流中第一个元素 67 Police police2 = policeList.stream().filter(Police::isPoliceIsRetire).findFirst().get(); 68 System.out.println("结果: " + police2); 69 } 70 }
四、归约
以上提供的方法,我们会发现返回给我们的都是一个boolean类型、无返回或为一个容器,可是我们有时需要将流中的每个元素重新组合来得到结果。这就需要将流中所有的元素结合着来处理,此时就需要归约。
1、例如将流中所有民警的年龄求和。当然使用for-each可以实现,可在这里我们还有更好的方式吗?
2、我们需要当前监狱年龄最大的民警和年龄最小的民警。此时又该如何实现呢?我们总不至于要将所有的民警挨个遍历一篇吧!
以上两个问题其实都是可以通过归约得到很好的解决,除此以外,我们会发现年龄都是与数值相关的,这也将引出我们普通流的变种,即数值流。下面以代码来说明问题:
package com.hz; import java.util.Arrays; import java.util.List; import java.util.Optional; public class ReduceDemo { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(5, 4, 3, 9); //***** 1-将一组数值求和 Integer sum = numbers.stream().reduce(0, (a, b) -> a + b); System.out.println("sum: " + sum); //若reduce未指定第一个参数,则不知实际返回类型,此时语言本身返回一个Optional容器对象 Optional<Integer> sumOptional = numbers.stream().reduce((a, b) -> a + b); System.out.println("sum-optional: " + sumOptional); //变化 sum = numbers.stream().reduce(0, Integer :: sum); System.out.println("sum2: " + sum); System.out.println("------------ 分割线 --------------"); //***** 2-获取一组数的最大和最小值 Optional<Integer> max = numbers.stream().reduce(Integer::max); System.out.println("max: " + max); Optional<Integer> min = numbers.stream().reduce(Integer :: min); System.out.println("min: " + min); System.out.println("---------- 分割线 --------"); //**** 3-一个案例:使用map和reduce统计民警列表中有多少民警 List<Police> policeList = Arrays.asList( new Police("P001", "余警官", 27, false), new Police("P002", "李警官", 32, false), new Police("P003", "程警官", 25, false), new Police("P004", "杨警官", 35, false), new Police("P005", "张警官", 70, true), new Police("P006", "司马警官", 68, true), new Police("P007", "赵科", 77, true), new Police("P008", "刘警官", 64, true) ); Integer policeNum = policeList.stream().map(p -> 1).reduce(0, Integer::sum); System.out.println("列表中民警数量: " + policeNum); } }
五、一个实例
关于流的这么多操作,我们上面都说了很多,但是在实际需求中我们应该如何的灵活运用呢?下面我们通过一些问题案例来总结一下流运用。
(1) 找出2019年入职的民警,并按年龄排序(从低到高)。
(2) 民警籍贯都在哪里?
(3) 查找所有来自于浙江的民警,并按姓名排序。
(4) 返回所有民警的警号,并按警号顺序排序。
(5) 有没有民警籍贯是在安徽的?
(6) 获取籍贯为浙江的民警的年龄之和
(7) 所有民警中,年龄最大的是多少?
(8) 找到民警中年龄最小的民警
package com.hz; import java.util.*; /** * 关于流操作的一个案例 */ public class StreamDemo { public static void main(String[] args) { List<Police> polices = Arrays.asList( new Police("P001", "Y警官", 27, "浙江", 2019), new Police("P002", "L警官", 32, "安徽", 2019), new Police("P003", "C警官", 25, "安徽", 2015), new Police("P004", "Y警官", 35, "浙江", 2015), new Police("P005", "Z警官", 31, "上海", 2018), new Police("P006", "W警官", 42, "浙江", 2018), new Police("P007", "Z警官", 31, "浙江", 2019), new Police("P009", "Z警官", 32, "浙江", 2019), new Police("P008", "L警官", 49, "浙江", 2019) ); // (1) 找出2019年入职的民警,并按年龄排序(从低到高)。 polices.stream().filter(p -> p.getPoliceEntryYear() == 2019).sorted(Comparator.comparing(Police::getPoliceAge)).forEach(System.out::println); System.out.println("---------------- 分割线 --------------------------"); // (2) 民警籍贯都在哪里? polices.stream().map(Police::getPoliceNativePlace).distinct().forEach(System.out::println); System.out.println("---------------- 分割线 --------------------------"); // (3) 查找所有来自于浙江的民警,并按姓名排序。 polices.stream().filter(p -> "浙江".equals(p.getPoliceNativePlace())).sorted(Comparator.comparing(Police::getPoliceName)).forEach(System.out::println); System.out.println("---------------- 分割线 --------------------------"); // (4) 返回所有民警的警号,并按警号顺序排序。 polices.stream().map(Police::getPoliceNo).sorted().forEach(System.out::println); System.out.println("---------------- 分割线 --------------------------"); // (5) 有没有民警籍贯是在安徽的? if (polices.stream().anyMatch(p -> "安徽".equals(p.getPoliceNativePlace()))) { System.out.println("存在籍贯为安徽的民警..."); } else { System.out.println("不存在籍贯为安徽的民警..."); } System.out.println("---------------- 分割线 --------------------------"); // (6) 获取籍贯为浙江的民警的年龄之和 Integer ageSum = polices.stream().filter(p -> "浙江".equals(p.getPoliceNativePlace())).map(Police::getPoliceAge).reduce(0, Integer::sum); //以上方式暗中存在一个装箱操作 其实stream中有个数值流可以省去这个隐藏操作 ageSum = polices.stream().filter(p -> "浙江".equals(p.getPoliceNativePlace())).mapToInt(Police::getPoliceAge).sum(); System.out.println("所有浙江民警年龄总和:" + ageSum); System.out.println("---------------- 分割线 --------------------------"); // (7) 所有民警中,年龄最大的是多少? Optional<Integer> ageMaxOptional = polices.stream().map(Police::getPoliceAge).reduce(Integer::max); // 或使用流的max方法 ageMaxOptional = polices.stream().max(Comparator.comparing(Police::getPoliceAge)).map(Police::getPoliceAge); //同样 该方式也可以转为数值流计算 OptionalInt maxAgeOp = polices.stream().mapToInt(Police::getPoliceAge).max(); System.out.println(maxAgeOp.getAsInt()); if (ageMaxOptional.isPresent()) { System.out.println("所有民警最大年龄为: " + ageMaxOptional.get()); } else { System.out.println("没有民警..."); } System.out.println("---------------- 分割线 --------------------------"); // (8) 找到民警中年龄最小的民警 Optional<Police> ageMinPoliceOptional = polices.stream().reduce((p1, p2) -> p1.getPoliceAge() < p2.getPoliceAge() ? p1 : p2); if (ageMinPoliceOptional.isPresent()) { System.out.println("年龄最小的民警: " + ageMinPoliceOptional); } else { System.out.println("列表中没有民警..."); } //其实还有更加简单的方式,流中有个min方法 ageMinPoliceOptional = polices.stream().min(Comparator.comparing(Police::getPoliceAge)); System.out.println(ageMinPoliceOptional); } static class Police { private String policeNo; private String policeName; private Integer policeAge; private Integer policeEntryYear; private String policeNativePlace; public Police(String policeNo, String policeName, Integer policeAge, String policeNativePlace, Integer policeEntryYear) { this.policeNo = policeNo; this.policeName = policeName; this.policeAge = policeAge; this.policeNativePlace = policeNativePlace; this.policeEntryYear = policeEntryYear; } public String getPoliceNo() { return policeNo; } public void setPoliceNo(String policeNo) { this.policeNo = policeNo; } public String getPoliceName() { return policeName; } public void setPoliceName(String policeName) { this.policeName = policeName; } public Integer getPoliceAge() { return policeAge; } public void setPoliceAge(Integer policeAge) { this.policeAge = policeAge; } public String getPoliceNativePlace() { return policeNativePlace; } public void setPoliceNativePlace(String policeNativePlace) { this.policeNativePlace = policeNativePlace; } public Integer getPoliceEntryYear() { return policeEntryYear; } public void setPoliceEntryYear(Integer policeEntryYear) { this.policeEntryYear = policeEntryYear; } @Override public String toString() { return "Police{" + "policeNo='" + policeNo + '\'' + ", policeName='" + policeName + '\'' + ", policeAge=" + policeAge + ", policeEntryYear='" + policeEntryYear + '\'' + ", policeNativePlace='" + policeNativePlace + '\'' + '}'; } } }
六、数值流及构建流
关于Java中流的介绍,我们也说明的差不多了,流对于表达数据处理查询是非常强大而有用的。不过在上面我们对流的创建都是基于集合之后做集合转换,可是除了集合之外,我们还有其他创建流的方式吗?答案是肯定的。关于数值流我们在上节实例中已经做了简单说明,在流的创建过程中,有时我们并不知道流的内容,此时需要根据变化来创建流对象。
1 package com.hz; 2 3 import java.io.IOException; 4 import java.nio.charset.Charset; 5 import java.nio.file.Files; 6 import java.nio.file.Paths; 7 import java.util.ArrayList; 8 import java.util.Arrays; 9 import java.util.List; 10 import java.util.stream.Stream; 11 12 /** 13 * 我们之前创建的流都是通过一个List对象或数组 14 * 其他方式也是可以创建流的 15 */ 16 public class CreateStreamDemo { 17 public static void main(String[] args) { 18 // 1-List创建流 19 List<String> list = new ArrayList<>(); 20 Stream<String> stream = list.stream(); 21 22 // 2-数组创建流 23 String[] strings = {"Gong", "Zi", "Qi"}; 24 Stream<String> stream1 = Arrays.stream(strings); 25 26 // 3-静态方法创建流 27 Stream<String> stream2 = Stream.of("Gong", "Zi", "Qi"); 28 Stream.iterate(0, i -> i + 2).limit(10).forEach(System.out::println); 29 Stream<Object> stream4 = Stream.generate(() -> "GongZiQi"); 30 //说明: iterate 和 generate 在生成流是需要注意截取,它们都可以做无限流的生成 如下 31 // Stream.iterate(0, i -> i + 2).forEach(System.out::println); //生成所有偶数 注意 32 // Stream.generate(Math::random).forEach(System.out::println); //生成所有随机数 0~1之间 注意 33 34 // 4-文件生成流 35 try { 36 Stream<String> stream3 = Files.lines(Paths.get("data.txt"), Charset.defaultCharset()); 37 } catch (IOException e) { 38 e.printStackTrace(); 39 } 40 } 41 }