Stream同样是Java8 中引入的一个新特性。它与传统的java.io中的Stream不同的是它并不用于字节传输,而是对集合、数组对象能力的增强。通过Stream操作可以把集合、数组中的对象进行过滤、分组、聚合、转换等复杂操作,把对Collection对象的复杂处理过程进行简化,减少代码的编写数量。
在大数据处理时代,巨量的数据处理会脱离RDBMS数据源。过去的Java API中少有对数据的批量处理方法,传统的解决方案都是在Iterator的迭代中完成数据加工。
Java7中要对年龄大于40的在职人员按工龄进行倒序排序后返回id,实现方法如下:
class EmployeeInfo { private String id; private String name;//姓名 private int age;//年龄 private int workingAge; //工龄 private double income;//收入 // ...更多属性 }
//在职员工列表 List<EmployeeInfo> employeeList = new Arraylist<>(); //过滤的列表 List<EmployeeInfo> filterList = new ArrayList<>(); for(EmployeeInfo e: employeeList){ if(e.getAge() > 40){ filterList.add(t); } } //排序 Collections.sort(filterList, new Comparator(){ public int compare(EmployeeInfo t1, EmployeeInfo t2){ return t2.getWorkingAge() - t1.getWorkingAge(); } }); //取id List<String> employeeIds = new ArrayList<>(); for(EmployeeInfo e: filterList){ employeeIds.add(e.getId()); }
Java8 中使用Stream的方式并使用并行计算可使代码简洁、高效
List<String> employeeIds = employeeList.stream() .filter(e->e.getAge() > 40)//过滤40岁以上 .sorted(Comparator.comparing(EmployeeInfo::getWorkingAge).reversed())//工齡倒序 .map(EmployeeInfo::getId)//提取id属性 .collect(toList());//转换列表
Stream是具有一系列的数据处理方法的迭代器(类似Iterator),是数据的管道,本身并不负责存储数据,也不会对源数据产生任何影响,我们可以称之为流,其特征正如流水
从集合(Collection)或数组中产生
Collection.stream()
Collection.parallelStream()
Arrays.stream(T array)
Stream静态方法
Stream.of(…)
Stream.generate(Supplier s) //无限生成
其它API
java.io.BufferedReader.lines()
Random.ints()
…
下面把Stream的操作进行完整分类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MNKoy59p-1618016771744)(https://i.loli.net/2020/07/09/i7hF3loQpI1RKHW.png)]
中间操作(Intermediate operations)
一个流可以后面跟随零个或多个 中间操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的,只有在指定的终结操作上才会执行中间操作。
无状态操作
处理流中的元素时,会对当前的元素进行单独处理。比如:过滤操作,因为每个元素都是被单独进行处理的,所有它和流中的其它元素无关,因此被称为无状态操作。
有状态操作
某个元素的处理可能依赖于其他元素。比如查找最小值,最大值,和排序,因为他们都依赖于其他的元素。因此为称为有状态操作。
终结操作(Terminal operations)
流只能有一个 终结操作,当这个操作执行后,流就被使用“光”了,无法再被操作,所以这必定是流的最后一个操作。终结操作的执行,才会真正开始流的遍历,并且会生成一个结果。
非短路操作
在完成Stream中所有元素迭代后才收集数据结果。
短路操作
返回boolean值,一般是操作一个无限的 Stream时,截断当前Stream,计算出结果。
筛选与切片(filter、distinct、limit、skip)
filter方法:参数是一个Predicate类型的方法引用或Lambda表达式,对stream的内容进行筛选
public void testFilter(){ Stream<Integer> s = Stream.of(1,2,3,4,5,6); s.filter(a->a>3)//过滤大于3的数字 .forEach(System.out::println); //输出4 5 6 }distinct方法:无参数,对stream的内容进行去重筛选
public void testDistinct(){ Stream s = Stream.of(1,1,2,2,2,3,4,5,6,5,2,4); s.distinct().forEach(System.out::println); //输出1 2 3 4 5 6 }limit方法:接收一个long类型的参数,从stream的头部保留对应数量的元素
public void testLimit(){ Stream<Integer> s = Stream.of(1,2,3,4,5,6); s.limit(4).forEach(System.out::println); //输出1 2 3 4 }skip方法:接收一个long类型的参数,从stream的头部开始跳过对应数量,保留剩下的元素
public void testSkip(){ Stream<Integer> s = Stream.of(1,2,3,4,5,6); s.skip(4).forEach(System.out::println); //输出5 6 }
收集(collect)
因为Stream没有存储作用,Stream在方法调用完成后会被关闭,所以Stream类提供了collect方法,作用是将stream中的对象收集到集合中。
收集到List
public void collectList(){ Stream<Integer> s = Stream.of(1,1,2,2,2,3,4,5,6,5,2,4); List<Integer> collect = s.collect(Collectors.toList()); }收集到Set
public void collectSet(){ Stream<Integer> s = Stream.of(1,1,2,2,2,3,4,5,6,5,2,4); Set<Integer> collect = s.collect(Collectors.toSet()); }收集到Map
public void collectMap(){ List<UserInfo> list = Arrays.asList( new UserInfo("张三","Zhang",23), new UserInfo("李四","Lee",24), new UserInfo("王五","King",25) ); //以姓名为key的map Map<String, UserInfo> collect = list.stream() .collect(Collectors.toMap(UserInfo::getName, Function.identity())); //Function.identity() 可以看作是t->t,代表返回对象本身 }分组收集
public void collectGroup(){ Stream<Integer> s = Stream.of(1,1,2,2,2,3,4,5,6,5,2,4); //把相同的数字分到一组 Map<Integer, List<Integer>> collect = s.collect(Collectors .groupingBy(Function.identity(), Collectors.toList())); //{1=[1, 1], 2=[2, 2, 2, 2], 3=[3], 4=[4, 4], 5=[5, 5], 6=[6]} //按条件分割 Map<Boolean, List<Integer>> partition = s.collect(Collectors.partitioningBy(a -> a > 3)); //{false=[1, 1, 2, 2, 2, 3, 2], true=[4, 5, 6, 5, 4]} }class UserInfo { private String name; private String department; private int age; } //按部门(department)分组 public void collectGroup(){ List<UserInfo> list = Arrays.asList( new UserInfo("张三","dev",23), new UserInfo("李四","test",24), new UserInfo("王五","dev",25) ); Map<String, List<UserInfo>> groupMap = list.stream().collect(Collectors .groupingBy(UserInfo::getDepartment,Collectors.toList())); /** {test=[TestCollect.UserInfo(name=李四, department=test, age=24)], dev=[TestCollect.UserInfo(name=张三, department=dev, age=23), TestCollect.UserInfo(name=王五, department=dev, age=25)]} */ }统计
public void sumnmary(){ Stream<Integer> s = Stream.of(1,1,2,2,2,3,4,5,6,5,2,4); //最小值 Optional<Integer> min = s.collect(Collectors.minBy(Integer::compareTo)); //最大值 Optional<Integer> max = s.collect(Collectors.maxBy(Integer::compareTo)); //平均值 Double d = s.collect(Collectors.averagingInt(a -> a)); //总和 Integer i = s.collect(Collectors.summingInt(a -> a)); //专门的统计方法 IntSummaryStatistics statistics = s.collect(Collectors.summarizingInt(Function.identity())); System.out.println(statistics.getSum()); System.out.println(statistics.getAverage()); System.out.println(statistics.getMax()); System.out.println(statistics.getMin()); System.out.println(statistics.getCount()); //summarizingLong/summarizingDouble }拼接字符串
public void join(){ Stream<String> s = Stream.of("a","b","c","d","e"); //String collect = s.collect(Collectors.joining(","));//a,b,c,d,e String collect = s.collect(Collectors.joining(",","[","]"));//[a,b,c,d,e] }
映射(map、flatmap)
map方法:参数是一个Function类型的接口、方法引用或Lambda表达式,可把stream中的元素转换成另一种类型
public void testMap(){ List<UserInfo> list = Arrays.asList( new UserInfo("张三","Zhang",23), new UserInfo("李四","Lee",24), new UserInfo("王五","King",25) ); //一对一 [用户信息,用户信息...] -> [姓名,姓名...] List<String> collect = list.stream() .map(UserInfo::getName)//把UserInfo对象映射成String对象 .collect(Collectors.toList()); } //一对多 [用户信息,用户信息...] -> [[姓名,昵称],[姓名,昵称]...] List<List<String>> collect2 = list.stream() .map(u -> Arrays.asList(u.getName(), u.getNickname())) .collect(Collectors.toList());flatmap方法:参数是一个返回类型为Stream的Function接口、方法引用或Lambda表达式,它可以将Stream中的元素统一抽取到一个新的Stream中
List<List<Integer>> list = Stream.of( Arrays.asList(1), Arrays.asList(2,3), Arrays.asList(4,5) ).collect(Collectors.toList()); //数据扁平化 List<Integer> collect = list1.stream() //Stream<List<Integer>> .flatMap(List::stream)//Stream<Stream<Integer>> .collect(Collectors.toList());[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kaH7tw29-1618016771745)(https://i.loli.net/2020/07/09/k3oFETp5zilM4RY.png)]
合并(reduce)
reduce方法:把当前Stream中的元素进行两两运算,生成一个只有一个值或对象的最终结果
//求和操作 Optional<Integer> sumo = Stream.of(1, 2, 3, 4) .reduce(Integer::sum); //带初始值的求和操作 Integer sum = Stream.of(1, 2, 3, 4) .reduce(0,Integer::sum);
字符统计
有本英文书要统计单词出现的次数
//单词 String str = "Java Stream Java Stream Stream SQL Java Stream API Java"; //单词计数 ConcurrentMap<String, Integer> collect = Arrays.stream(str.split(" ")) .filter(word -> word.length() > 0) .collect(Collectors.toConcurrentMap(Function.identity(), w -> 1, Integer::sum));
行列转换
数据库中的存储是按年的行记录,前端报表需要将行记录
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-27ua69jz-1618016771748)(https://i.loli.net/2020/07/09/VdfnuxWHyXeCa7k.png)]
输出JSON的数据结构为
[ {year: 2019,population: 435543,populationf: 412042,populationl: 148768,populationw: 135665}, {year: 2018,population: 413298,populationf: 382551,populationl: 137763,populationw: 123986}, ...... ]数据要在前端进行图表展示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sOqGKGbm-1618016771749)(https://i.loli.net/2020/07/09/iJv9eI4LlnRfE5q.png)]
前面图表要求的JSONs数据结构为
[ {year: 2019,x: '人口总数',y: 435543}, {year: 2019,x: '农业人口总数',y: 412042}, {year: 2019,x: '劳动力资源总数',y: 148768}, {year: 2019,x: '从业人员数量',y: 135665}, {year: 2018,x: '人口总数',y: 413298}, {year: 2018,x: '农业人口总数',y: 382551}, {year: 2018,x: '劳动力资源总数',y: 137763}, {year: 2018,x: '从业人员数量',y: 123986}, ]Java代码
//数据库查询每年的记录 List<DataPopulationDTO> dataPopulationDTOS = baseDao.summaryPopulationByBeforeYear(year); List<ChartData3> list = dataPopulationDTOS.stream()//Stream<DataPopulationDTO> .sorted(Comparator.comparing(DataPopulationDTO::getYear))//按年排序 .map(d->{//行列转换,把行数据转列数据,每年的一条数据变成4条 List<ChartData3> list = new ArrayList<>(); ChartData3 data = new ChartData3(d.getYear(),"人口总数",d.getPopulation()); ChartData3 data1 = new ChartData3(d.getYear(),"农业人口总数",d.getPopulationf()); ChartData3 data2 = new ChartData3(d.getYear(),"劳动力资源总数",d.getPopulationl()); ChartData3 data3 = new ChartData3(d.getYear(),"从业人员数量",d.getPopulationw()); list.add(data); list.add(data1); list.add(data2); list.add(data3); return list; })//Stream<List<ChartData3>> .flatMap(List::stream)//数据扁平化,二维数据变一维 Stream<Stream<ChartData3>> - > Stream<ChartData3> .collect(Collectors.toList());//收集到List
Optional是Java 8引入的类,用于表示可能存在的对象,引入的目的是尽可能地避免NullPointerException。
Optional.of和Optional.ofNullable
//of方法接收一个非空的对象,如果对象是null会抛出异常 Optional<String> optional = Optional.of("str"); //如果不确定构建Optional的参数对象是否为空,可以用ofNullable方法 String str = null; Optional<String> os = Optional.ofNullable(str);
Optional<String> op = Optional.of("an Optional"); //与Stream一样,Optional接收一个Predicate函数,如果结果为false返回空的Optional Optional<String> b = os.filter(s -> s.contains("d")); //也可以调用map和flatmap方法 //返回Optional中的值,如果没有值会抛出NoSuchElementException异常 String value = op.get(); //判断Optional中是否有值 boolean isPresent = op.isPresent(); //判断如果有值的话做某些处理 op.ifPresent(System.out::println); //String::toUppercase //判断如果没有值的话抛出异常处理 op.orElseThrow(()->{ //记录日志 log.error("值不能为空"); //抛出异常 return new RuntimeException("dfdf"); }); //当Optional的值不为空,返回值,当Optional为空时,返回orElse中的值 String svalue = op.orElse("some value"); //接收Lambda表达式或方法引用的API String fvalue = op.orElseGet(()->"get from function");
orElse与orElseGet的区别
参数
orElse接收一个对象实例,orElseGet接收方法引用或Lambda表达式
调用
orElse不论Optional的值是否为空都会执行,orElseGet仅在Optional的值为空时才执行
Stream的特性总结