这篇博客仅谈论Stream的基础使用,不涉及Stream效率的问题以及并行流的讲解。等时机成熟,再更下一篇。
Stream能针对集合,数组等有限的数据,甚至是无限的数据进行操作。这种操作可以是多种多样的,比如过滤掉集合中不满足条件的数据,或是将集合转换为另外一种形式,而这种操作仅仅是一行的代码就能解决,简洁且优雅。
比如:
从一系列Employee对象中过滤工资低于2000的员工,然后按照工资降序排列,最后打印输出。
常规代码:
List<Employee> employeeList = Arrays.asList( new Employee("a", 20, 1000), new Employee("b", 30, 3000), new Employee("c", 35, 2500), new Employee("d", 22, 7000), new Employee("e", 37, 10000) ); List<Employee> employeeListAfterFiltering = new ArrayList<>(); for (Employee employee: employeeList) { if(employee.getSalary()>=2000) employeeListAfterFiltering.add(employee); } employeeListAfterFiltering.sort((x,y) ->y.getSalary() -x.getSalary()); employeeListAfterFiltering.forEach(System.out::println);Stream代码:
Stream.of( new Employee("a", 20, 1000), new Employee("b", 30, 3000), new Employee("c", 35, 2500), new Employee("d", 22, 7000), new Employee("e", 37, 10000) ).filter(x->x.getSalary()>=2000).sorted((x, y)-> y.getSalary()-x.getSalary()).forEach(System.out::println);Stream使用步骤分为三个步骤:
创建一个流。指定一个流转换为另外一个流的中间操作,此操作可以有多个。终止操作,此操作会执行强制之前的中间操作(中间操作是惰性操作,后面会解释)。从此以后,该流就不能够再使用。需要注意两点:
1.中间操作都是将一个流转换为另一个流的操作。
2.终止操作一旦执行,最开始创建的流就不能再使用,否则会抛出IllegalStateException异常。验证如下:
测试代码:
@Test public void testBug(){ List<Employee> employeeList = Arrays.asList( new Employee("a", 20, 1000), new Employee("b", 30, 3000), new Employee("c", 35, 2500), new Employee("d", 22, 7000), new Employee("e", 37, 10000) ); Stream<Employee> stream = employeeList.stream(); stream.filter(x->x.getAge()>20).forEach(System.out::println); //第二次使用流会报错 stream.filter(x->x.getAge()>20).forEach(System.out::println); }测试结果:
Stream不会存储元素,这些元素要么是存储在底层的集合中,要么是按需生成的。比如无限流就是一个比较极端的例子,它不可能存储在Stream里面。
流的操作并不会修改数据源,即流的操作并不会影响到用于产生流的集合,数组等等。
流的操作是尽可能惰性执行,只要当终止操作执行时,才会强制执行中间操作。
这里面我个人理解有两层意思:
其一,体现惰性操作的优越性。充分分析中间操作后,再对中间操作进行批处理。比如针对无限流的一个例子,首先的操作是采用peek进行打印测试,然后再通过limit限制无限流个数,显然按照正常的想法来,程序必然在peek步骤死循环,但是,实际情况如下:
测试代码:
@Test public void testInertiaOperation(){ Stream.iterate(0,x->x+2).peek(System.out::print).limit(5).forEach(x->{}); }结果:
02468
其二,**体现惰性操作的存在性。**因为中间操作都是将一个流转化为另一种流,根据这个特性设计如下代码:
测试代码:
@Test public void testInertiaOperation1(){ Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5).peek(System.out::println); System.out.println("我猜测我先于中间操作执行"); stream.forEach(x->{}); }结果:
我猜测我先于中间操作执行 1 2 3 4 5
这表明惰性操作是的确存在的。
根据数据源的不同以及涉及到的类的不同,分为如下三种创建类的方式。
通过集合创建通过数组创建通过Stream创建当然,创建流的方式不止上面三种,仅挑常用的来讲。
Collection及其子类都有一个实例方法stream(),利用该方法就能通过集合创建流。
测试代码:
Collection<Integer> collection = new HashSet<>(); Stream<Integer> stream = collection.stream();Arrays有一个静态方法stream(T[])可以把数组转为流。
测试代码:
Integer [] arr = {1,2,3,4}; Stream<Integer> integerStream2 = Arrays.stream(arr);事实上,Stream可以通过可变参数列表创建流;创建仅一个元素的流;创建无限流;创建空流。
通过可变参数列表创建流Stream.of(T ...values) : Stream<T>
测试代码:
Stream<Integer> integerStream = Stream.of(1, 2, 3);创建一个元素的流Stream.of(T t) : Stream<T>
测试代码:
Stream<Integer> integerStream = Stream.of(1);通过静态方法创建无限流
测试代码:
/** * 提供一个Supplier产生无限流 * Stream.generate( Supplier<T> s) : Stream<T> * 模拟创建一个 范围在0--100的无限流 */ Stream<Integer> integerStream1 = Stream.generate(() -> new Random().nextInt(101)); /** 提供一个seed 即初始值,和一个UnaryOperator产生无限流,UnaryOperator继承自Function<T,T> * Stream.iterate(final T seed, final UnaryOperator<T> f) : Stream<T> * 可以将f理解为一个函数,初始化值(第一个值)就是seed ,第二个值就是f(seed),第三个值就是 * f((seed)),如此类推。 * * initial(即seed) , f(x) * 1: initial * 2: f(initial * 3: f(f(initial)) * ...... */ Stream<Integer> integerStream3 = Stream.iterate(0, x -> x + 2);通过静态方法创建空流
测试代码:
/** * Stream的静态方法创建空流 */ Stream<Object> empty = Stream.empty();为了方便演示中间操作,提前讲一个终止操作 void forEach(Consumer<? super T> action),该终止操作会对流中的每个元素都执行一个action操作。比如打印输出。
除此以外,为了方便演示,写了一个实体类(包含姓名,年龄,工资属性)
Employee:
package com.lordbao.entity; import java.util.Objects; /** * @Author Lord_Bao * @Date 2020/10/5 11:31 * @Version 1.0 */ public class Employee { private String username; private int age; private int salary; public Employee() { } public Employee(String username, int age, int salary) { this.username = username; this.age = age; this.salary = salary; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getSalary() { return salary; } public void setSalary(int salary) { this.salary = salary; } @Override public String toString() { return "Employee{" + "username='" + username + '\'' + ", age=" + age + ", salary=" + salary + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Employee employee = (Employee) o; return age == employee.age && salary == employee.salary && Objects.equals(username, employee.username); } @Override public int hashCode() { return Objects.hash(username, age, salary); } }Stream<T> filter(Predicate<? super T> predicate)通过向filter方法传入一个predicate参数,以此过滤掉那些不满足条件的元素,并返回一个满足条件的元素的新流。
场景描述:
取出集合中工资大于3000的员工并打印输出
测试代码:
@Test public void testFilter(){ List<Employee> employeeList = Arrays.asList( new Employee("a", 20, 1000), new Employee("b", 30, 3000), new Employee("c", 35, 2500), new Employee("d", 22, 7000), new Employee("e", 37, 10000) ); // 取出集合中工资大于3000的并打印输出 employeeList.stream().filter(x->x.getSalary()>3000).forEach(System.out::println); }测试结果:
Employee{username=‘d’, age=22, salary=7000} Employee{username=‘e’, age=37, salary=10000}
<R> Stream<R> map(Function<? super T,? extends R> mapper)可以将Stream中的元素转换为其他的元素,并返回包含新元素的流。
场景描述:
取出集合中工资大于3000的员工的名字并打印输出
测试代码:
@Test public void testMap(){ List<Employee> employeeList = Arrays.asList( new Employee("a", 20, 1000), new Employee("b", 30, 3000), new Employee("c", 35, 2500), new Employee("d", 22, 7000), new Employee("e", 37, 10000) ); // 取出集合中工资大于3000的名字并打印输出 employeeList.stream().filter(x->x.getSalary()>3000).map(Employee::getUsername).forEach(System.out::println); }测试结果:
d e
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)。此方法是将每个元素映射成一个流,然后将这些流的中的元素都取出来合并,然后返回合并后的新流。
按照这个逻辑将Stream中的每个元素都映射为Stream,然后将每个Stream都拆解,将拆解后的元素组装成
新的Stream。貌似看起来有点模糊,不如Map好理解啊。
其实这里可以这么分析,什么元素能够映射为Stream呢?换个问题就是Stream是怎么创建的?大多数是通过集合,数组呗。那么我可不可以理解为本来的数据源就是集合或是数组,而它存储的元素恰好也是集合或是数组呢?
按照这个思路,我设计了如下的例子。
测试代码:
@Test public void testFlatMap2(){ //1.二维数组 Employee [] [] employeeArr1 ={ { new Employee("a", 20, 1000),new Employee("c", 35, 2500)}, { new Employee("b", 30, 3100),new Employee("d", 22, 6900),}, { new Employee("e", 37, 10000)} }; Stream.of(employeeArr1).flatMap(Stream::of).forEach(System.out::println); System.out.println("..................分隔..................."); //2.一维数组 存储元素为List List<Employee>[] employeeArr2= new List[]{ Arrays.asList(new Employee("a", 20, 1000), new Employee("c", 35, 2500)), Arrays.asList(new Employee("b", 30, 3100), new Employee("d", 22, 6900)), Arrays.asList(new Employee("e", 37, 10000)) }; Stream.of(employeeArr2).flatMap(List::stream).forEach(System.out::println); System.out.println("..................分隔..................."); //3.List 存储 List List<List<Employee>> employeeList1 = Arrays.asList( Arrays.asList(new Employee("a", 20, 1000), new Employee("c", 35, 2500)), Arrays.asList(new Employee("b", 30, 3100), new Employee("d", 22, 6900)), Arrays.asList(new Employee("e", 37, 10000))); employeeList1.stream().flatMap(List::stream).forEach(System.out::println); System.out.println("..................分隔..................."); //4.List 存储 数组 List<Employee[]> employeeList2 = Arrays.asList( new Employee[]{new Employee("a", 20, 1000), new Employee("c", 35, 2500)}, new Employee[]{new Employee("b", 30, 3100), new Employee("d", 22, 6900)}, new Employee[]{new Employee("e", 37, 10000)} ); employeeList2.stream().flatMap(Stream::of).forEach(System.out::println); }测试结果:
Employee{username=‘a’, age=20, salary=1000} Employee{username=‘c’, age=35, salary=2500} Employee{username=‘b’, age=30, salary=3100} Employee{username=‘d’, age=22, salary=6900} Employee{username=‘e’, age=37, salary=10000} …分隔… Employee{username=‘a’, age=20, salary=1000} Employee{username=‘c’, age=35, salary=2500} Employee{username=‘b’, age=30, salary=3100} Employee{username=‘d’, age=22, salary=6900} Employee{username=‘e’, age=37, salary=10000} …分隔… Employee{username=‘a’, age=20, salary=1000} Employee{username=‘c’, age=35, salary=2500} Employee{username=‘b’, age=30, salary=3100} Employee{username=‘d’, age=22, salary=6900} Employee{username=‘e’, age=37, salary=10000} …分隔… Employee{username=‘a’, age=20, salary=1000} Employee{username=‘c’, age=35, salary=2500} Employee{username=‘b’, age=30, salary=3100} Employee{username=‘d’, age=22, salary=6900} Employee{username=‘e’, age=37, salary=10000}
那么FlatMap有什么作用呢?很显然,它非常适合于那些存储元素本身就是集合或数组 的集合或数组。
模拟场景:
按照工资从3000以下,3000到7000,7000以上分为三个档次(已经用数组存储好),现在要求从这些员工中,挑选出年龄大于20的员工,并打印输出。
思路分析:
可以用一个List或是数组存储好已经分好的数组,然后根据flatMap的特性以及filter过滤掉年龄小于等于20岁的员工,并打印输出。
测试代码:
@Test public void testFlatMap(){ // 工资3000以下 Employee [] arr1 ={ new Employee("a", 20, 1000),new Employee("c", 35, 2500)}; // 工资3000--7000 Employee [] arr2 = { new Employee("b", 30, 3100),new Employee("d", 22, 6900)}; // 工资7000以上 Employee [] arr3= { new Employee("e", 37, 10000)}; Employee [] [] employeeArr ={ arr1,arr2,arr3 }; Arrays.stream(employeeArr).flatMap(Stream::of).filter(ele->ele.getAge()>20).forEach(System.out::println); }测试结果:
Employee{username=‘c’, age=35, salary=2500} Employee{username=‘b’, age=30, salary=3100} Employee{username=‘d’, age=22, salary=6900} Employee{username=‘e’, age=37, salary=10000}
现在考虑一个更为广泛的场景
场景描述:
提供一个方法,要求该方法可以传入集合,数组,单个对象,并且有多少数据就可以传入多少数据。而且还能够根据一定的条件,筛选掉一定的数据。最后打印输出结果。
思路分析:
根据传入数据多少不限,那么表明参数应当为可变参数列表。根据一定的条件,这说明还要提供一个Predicate参数。
下面我自己提供了3组数据,类型分别是对象,集合,数组。条件是过滤掉工资小于1000的员工,并要求分别用传统的方法和flatMap方法进行解决。
主程序
@Test public void testFlatMap3(){ Employee a = new Employee("a", 20, 1000); Employee [] employeeArr = {new Employee("b", 30, 3000), new Employee("c", 35, 2500)}; List<Employee> employeeList = Arrays.asList(new Employee("d", 22, 7000), new Employee("e", 37, 10000)); filterAndPrint(ele->ele.getSalary()>1000,a,employeeArr,employeeList); System.out.println(".........................分割线........................."); filterAndPrint2(ele->ele.getSalary()>1000,a,employeeArr,employeeList); }传统方法:
public static void filterAndPrint2(Predicate<Employee> predicate,Object ... obj){ for (Object o:obj){ if (o.getClass().isArray()){ Employee [] arr =(Employee[])o; for (Employee employee:arr){ if (predicate.test(employee)) System.out.println(employee); } }else if (o.getClass().equals(Employee.class)) { if (predicate.test((Employee)o)) System.out.println(o); }else{ Collection<Employee> collection= (Collection<Employee>)o; List<Employee> employeeList= new ArrayList<>(); employeeList.addAll(collection); for (Employee employee :employeeList){ if (predicate.test(employee)){ System.out.println(employee); } } } } }FlatMap方法:
public static void filterAndPrint(Predicate<Employee> predicate,Object ... obj){ Arrays.asList(obj).stream().flatMap( ele -> { if (ele.getClass().isArray()) { //如果是数组 return Arrays.stream((Employee[])ele); } else if (ele.getClass().equals(Employee.class)) { //如果仅仅是一个Employee对象 return Stream.of(ele); } else { return ((Collection<Employee>) ele).stream();//如果是一个集合 } } ).filter(ele->predicate.test((Employee)ele)).forEach(System.out::println); }测试结果:
Employee{username=‘b’, age=30, salary=3000} Employee{username=‘c’, age=35, salary=2500} Employee{username=‘d’, age=22, salary=7000} Employee{username=‘e’, age=37, salary=10000} …分割线… Employee{username=‘b’, age=30, salary=3000} Employee{username=‘c’, age=35, salary=2500} Employee{username=‘d’, age=22, salary=7000} Employee{username=‘e’, age=37, salary=10000}
具体采用哪种方案,其实不好说,一种是分开治理,一种是集中处理。我觉得其实传统方法也还看得过去…
直接给出API
//限制流的元素最多为maxSize个 Stream<T> limit(long maxSize) //跳过前n个元素 Stream<T> skip(long n) //将类型相同的流合并 static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)测试代码:
@Test public void testSkipAndLimitAndConcat(){ //skip 跳过前n个元素 Integer [] arr ={1,2,3,4,5,6,7,8,9,10}; //跳过前5个元素并打印 Stream<Integer> stream1 = Stream.of(arr).skip(5); stream1.forEach(System.out::println); System.out.println("..............分割线.............."); //limit 限制流的元素个数 常和无限流一起用 //从0--100中随机取出5个数字并打印 Stream<Integer> stream2= Stream.generate(()->new Random().nextInt(101)).limit(5); stream2.forEach(System.out::println); System.out.println("..............分割线.............."); //concat 拼接流 Stream<Integer> stream3 = Stream.generate(() -> new Random().nextInt(101)).limit(2); Stream<Integer> stream4 = Stream.generate(() -> new Random().nextInt(101)).limit(3); Stream.concat(stream3,stream4).forEach(System.out::println); }测试结果:
6 7 8 9 10 …分割线… 36 73 36 10 74 …分割线… 69 23 27 67 90
Stream<T> distinct()通过元素的equals方法去掉重复的元素,并返回新流。
测试代码:
@Test public void testDistinct(){ Integer [] arr ={1,1,2,2,2,3,3}; Stream.of(arr).distinct().forEach(System.out::println); }测试结果:
1 2 3
排序有两种方式,一种是根据元素自己实现的Comparable的方法来进行排序,另外一种是传入比较器Comparator来进行排序.
Stream<T> sorted(); Stream<T> sorted(Comparator<? super T> comparator);测试代码:
@Test public void testSorted(){ String [] arr ={"a","b","c","d","e"}; //顺序输出 Stream.of(arr).sorted().forEach(System.out::println); System.out.println("..............分割线.............."); //逆序输出 Stream.of(arr).sorted(Comparator.reverseOrder()).forEach(System.out::println); }测试结果:
a b c d e …分割线… e d c b a
Stream<T> peek(Consumer<? super T> action)该方法会返回与原来元素相同的流,但是会对每个元素额外执行一个action操作。这个方法常用来进行测试。
测试代码:
@Test public void testPeek(){ Stream.of(1,2,3,4).peek(System.out::println).forEach(ele ->{}); }测试结果:
1 2 3 4
终止操作强制执行之前的中间操作,并产生结果。这些结果大致可以分为三类
遍历操作流中的元素约简操作(如获取流元素的最大值,流元素个数等等)将流转换为集合或是数组或是Map遍历操作可以分为三类:
//获取元素的迭代器 Iterator<T> iterator(); //对流中每个元素进行action操作(并行流是无序的) void forEach(Consumer<? super T> action); //对流中每个元素按照在流中的顺序进行操作 void forEachOrdered(Consumer<? super T> action);测试代码:
@Test public void testTraverse(){ Integer [] arr ={1,2,3,4,5}; //1.获取流的迭代器,然后遍历 Iterator<Integer> iterator = Stream.of(arr).iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); } System.out.println("..................分隔..................."); //2.forEach Stream.of(arr).parallel().forEach(System.out::println); System.out.println("..................分隔..................."); //3.forEachOrdered Stream.of(arr).forEachOrdered(System.out::println); }测试结果:
1 2 3 4 5 …分隔… 3 2 5 1 4 …分隔… 1 2 3 4 5
在这里将约简操作分为两类,一类特指reduce方法,另一类指非reduce方法的其他方法。首先,介绍非reduce方法的其他简单方法。
API如下:
//返回元素的个数 long count(); //根据比较器,返回最大值 Optional<T> max(Comparator<? super T> comparator); //根据比较器,返回最小值 Optional<T> min(Comparator<? super T> comparator); //返回Stream中的任意一个元素 Optional<T> findAny(); //返回Stream中的第一个元素 Optional<T> findFirst(); //检验Stream是否有元素满足predicate条件 boolean anyMatch(Predicate<? super T> predicate); //检验Stream的元素是否都不满足predicate条件 boolean noneMatch(Predicate<? super T> predicate); //检验Stream的元素是否都满足predicate条件 boolean allMatch(Predicate<? super T> predicate); //获得汇总统计 通过汇总统计 可以获得元素个数 最大值 最小值 平均即 求和 // IntSummaryStatistics 相应的 有LongSummaryStatistics DoubleSummaryStatistics IntSummaryStatistics collect(Collectors.summarizingInt(ToIntFunction<? super T> mapper)测试代码:
@Test public void testReduce2(){ List<Employee> employeeList = Arrays.asList( new Employee("a", 20, 1000), new Employee("b", 30, 3000), new Employee("c", 35, 2500), new Employee("d", 22, 7000), new Employee("e", 37, 10000)); // count 放回 Stream 中元素个数 long count = employeeList.stream().count(); System.out.println(count); // 返回工资最大的员工 Optional<Employee> maxEmployee = employeeList.stream().max(Comparator.comparingInt(Employee::getSalary)); System.out.println(maxEmployee.get()); // 返回工资最小的员工 Optional<Employee> minEmployee = employeeList.stream().min(Comparator.comparingInt(Employee::getSalary)); System.out.println(minEmployee.get()); // 依次按照工资降序,年龄降序,姓名升序进行排列,然后返回第一个员工 Optional<Employee> firstEmployee = employeeList.stream().sorted((x, y) -> { if (x.getSalary() == y.getSalary()) { if (x.getAge() == y.getAge()) { return x.getUsername().compareTo(y.getUsername()); } else { return y.getAge() - x.getAge(); } } else return y.getSalary() - x.getSalary(); }).findFirst(); System.out.println(firstEmployee.get()); //返回年龄超过30的任意一个员工 Optional<Employee> anyEmployee = employeeList.stream().filter(x -> x.getAge() > 30).findAny(); System.out.println(anyEmployee.orElse(new Employee())); //查看所有员工是不是都成年了 Predicate<Employee> predicate = x ->x.getAge()>=18; boolean isAllMatch = employeeList.stream().allMatch(predicate); boolean isNoneMatch = employeeList.stream().noneMatch(predicate); boolean isAnyMatch = employeeList.stream().anyMatch(predicate); System.out.println(isAllMatch); System.out.println(isNoneMatch); System.out.println(isAnyMatch); //获得汇总统计 通过汇总统计 可以获得元素个数 最大值 最小值 平均即 求和 IntSummaryStatistics summaryStatistics = employeeList.stream().collect(Collectors.summarizingInt(Employee::getSalary)); System.out.println(summaryStatistics.getCount()); System.out.println(summaryStatistics.getMin()); System.out.println(summaryStatistics.getMax()); System.out.println(summaryStatistics.getAverage()); System.out.println(summaryStatistics.getSum()); }测试结果:
5 Employee{username=‘e’, age=37, salary=10000} Employee{username=‘a’, age=20, salary=1000} Employee{username=‘e’, age=37, salary=10000} Employee{username=‘c’, age=35, salary=2500} true false true
5 1000 10000 4700.0 23500
现在讲reduce方法。
API如下:
Optional<T> reduce(BinaryOperator<T> accumulator); T reduce(T identity,BinaryOperator<T> accumulator); <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)第一个方法,用官方文档的解释最好:
boolean foundAny = false; T result = null; for (T element : this stream) { if (!foundAny) { foundAny = true; result = element; } else result = accumulator.apply(result, element); } return foundAny ? Optional.of(result) : Optional.empty();第二个方法,用官方文档的解释也最好:
T result = identity; for (T element : this stream) result = accumulator.apply(result, element) return result;其中Identity是幺元值,什么是幺元值呢?定义一种二元运算 ? 如果 e ?x = x。那么e就是针对?运算的幺元值。比如说 加法的幺元值是 0 ,乘法的幺元值是1.
除此以外,为了保证并发的计算结果总是相同的,要求accumlator针对的运算必须是可结合的。
第三个方法,用官方文档的解释也最好
U result = identity; for (T element : this stream) result = accumulator.apply(result, element) return result;不过官方文档还提到了identity必须是combiner的幺元值,并且要满足如下公式:combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t).这个公式等一会儿解释,在此之前我们
要弄懂这个方法是干什么的。
官方文档有这样一段解释:
Many reductions using this form can be represented more simply by an explicit combination of map and reduce operations. The accumulator function acts as a fused mapper and accumulator, which can sometimes be more efficient than separate mapping and reduction, such as when knowing the previously reduced value allows you to avoid some computation.
简单来说,就是这个reduce方法是一个map-reduce的融合体,它有时候比单独的先map后reduce的效率更高。
现在再来看一下,该方法的伪代码
U result = identity; for (T element : this stream) result = accumulator.apply(result, element) return result;accumulator.apply的第一个参数result类型为U,第二个参数element类型为T(即Stream的元素类型),计算结果result是个U。其实说白了可以理解为先将第二个参数T映射(map)为一个U,然后再与第一个参数进行计算(reduce),产生结果result。
而我的猜测是combiner的作用(也即那个公式)就是定义这个计算。当然不一定对,有的博客说是parallelStream reduce操作是并发进行的 为了避免竞争 每个reduce线程都会有独立的result,combiner的作用在于合并每个线程的result得到最终结果。这里暂时持保留态度,有时间测试。
情景描述:
计算员工的总工资。
测试代码:
@Test public void testReduce(){ List<Employee> employeeList = Arrays.asList( new Employee("a", 20, 1000), new Employee("b", 30, 3000), new Employee("c", 35, 2500), new Employee("d", 22, 7000), new Employee("e", 37, 10000) ); Optional<Integer> result = employeeList.stream().map(Employee::getSalary).reduce(Integer::sum); System.out.println(result.get()); Integer result2 = employeeList.stream().map(Employee::getSalary).reduce(0, Integer::sum); System.out.println(result2); // 第三个参数类似定义一个运算 ,这个运算满足结合律 且这个运算的幺元就是identity Integer result3 = employeeList.stream().reduce(0, (x, y) -> x + y.getSalary(), Integer::sum); System.out.println(result3); }测试结果:
23500 23500 23500
查看API:
//转换为Object数组 Object[] toArray(); //转换为指定类型的数组 <A> A[] toArray(IntFunction<A[]> generator)测试代码:
@Test public void testStreamToArray(){ List<Employee> employeeList = Arrays.asList( new Employee("a", 20, 1000), new Employee("b", 30, 3000), new Employee("c", 35, 2500), new Employee("d", 22, 7000), new Employee("e", 37, 10000) ); // A[] toArray(intFunction<A[]>) generator) Employee[] employees = employeeList.stream().filter(x -> x.getAge() > 20).toArray(Employee[]::new); for (Employee employee: employees) { System.out.println(employee); } System.out.println("--------------分割线-------------"); //Object[] toArray() Object[] array = employeeList.stream().filter(x -> x.getAge() > 20).toArray(); for (Object obj:array){ System.out.println(obj); } }测试结果:
Employee{username=‘b’, age=30, salary=3000} Employee{username=‘c’, age=35, salary=2500} Employee{username=‘d’, age=22, salary=7000} Employee{username=‘e’, age=37, salary=10000} …分割线… Employee{username=‘b’, age=30, salary=3000} Employee{username=‘c’, age=35, salary=2500} Employee{username=‘d’, age=22, salary=7000} Employee{username=‘e’, age=37, salary=10000}
直接上方法:
//将Stream转为List List<T> collect(Collectors.toList()); //将Stream转为Set Set<T> collect(Collectors.toSet()); //通过collectionFactory将Stream转为更具体的集合 如ArrayList HashSet C<T> collect(Collectors.toCollection(Suppiler <C> collecitionFactory)测试代码:
@Test public void testStreamToCollection(){ List<Employee> employeeList = Arrays.asList( new Employee("a", 20, 1000), new Employee("b", 30, 3000), new Employee("c", 35, 2500), new Employee("d", 22, 7000), new Employee("e", 37, 10000) ); //转换为 List Set List<Employee> employeeList1 = employeeList.stream().filter(x -> x.getAge() > 20).collect(Collectors.toList()); Set<Employee> employeeSet = employeeList.stream().filter(x -> x.getAge() > 20).collect(Collectors.toSet()); //转换为 更具体的集合 比如 HashSet 或是 ArrayList HashSet<Employee> employeeHashSet = employeeList.stream().filter(x -> x.getAge() > 20).collect(Collectors.toCollection(HashSet::new)); ArrayList<Employee> employeeArrayList = employeeList.stream().filter(x -> x.getAge() > 20).collect(Collectors.toCollection(ArrayList::new)); }见API
//针对元素为String类型的流 将String都拼接起来 String collect(Collectors.joining()); //针对元素为String类型的流 将String都拼接起来,用分隔符分隔 String collect(Collectors.joining(CharSequence delimiter)); //针对元素为String类型的流 将String都拼接起来,用分隔符分隔 并增加前后缀 String collect(Collectors.joiningjoining(CharSequence delimiter,CharSequence prefix,CharSequence suffix);测试代码:
@Test public void testCollectorsJoining(){ List<Employee> employeeList = Arrays.asList( new Employee("a", 20, 1000), new Employee("b", 30, 3000), new Employee("c", 35, 2500), new Employee("d", 22, 7000), new Employee("e", 37, 10000) ); //针对 String类型流 joining 将字符串相连 String namesString = employeeList.stream().map(Employee::getUsername).collect(Collectors.joining()); System.out.println(namesString); //针对 String类型流 joining 将字符串相连 分隔符进行区分 String namesStringWithDelimiter = employeeList.stream().map(Employee::getUsername).collect(Collectors.joining(",")); System.out.println(namesStringWithDelimiter); //针对 String类型流 joining 将字符串相连 分隔符进行区分 并添加前缀和后缀 String namesStringWithPrefixAndSuffix = employeeList.stream().map(Employee::getUsername).collect(Collectors.joining(",", "NAME: \n", "\n以上")); System.out.println(namesStringWithPrefixAndSuffix); }测试结果:
abcde a,b,c,d,e NAME: a,b,c,d,e 以上
有时候需要将Stream中的元素转为一个Map,比如Employee的username作为Key,Employee作为Value。
详见API:
// 利用此方法可以将Stream中的元素转为Map static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper); // 利用此方法可以将Stream中的元素转为Map ,并根据mergeFunction解决主键冲突 static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction); // 利用此方法可以将Stream中的元素转为Map ,并根据mergeFunction解决主键冲突,根据mapSupplier转为特定的Map 如 TreeMap HashMap static <T, K, U, M extends Map<K, U>> Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper,BinaryOperator<U> mergeFunction,upplier<M> mapSupplier);测试代码:
@Test public void testStreamToMap(){ List<Employee> employeeList = Arrays.asList( new Employee("a", 20, 1000), new Employee("b", 30, 3000), new Employee("c", 35, 2500), new Employee("d", 22, 7000), new Employee("e", 37, 10000) ); //Function.identity() 会返回一个Function 该Function 传入的之值 和 输出的值 一样 //等将于 ele -> ele Map<String, Employee> employeeMap = employeeList.stream().collect(Collectors.toMap(Employee::getUsername, Function.identity())); employeeMap.forEach((k,v)-> System.out.println("key:"+k+" , value:"+v)); System.out.println("......................分隔............................."); Map<String, Integer> integerMap = employeeList.stream().collect(Collectors.toMap(Employee::getUsername, Employee::getAge)); integerMap.forEach((k,v)-> System.out.println("key:"+k+" , value:"+v)); System.out.println("......................分隔............................."); //为了避免冲突,新的值覆盖旧的值 List<Employee> employeeList1 = Arrays.asList( new Employee("a", 20, 1000), new Employee("b", 30, 3000), new Employee("c", 35, 2500), new Employee("d", 22, 7000), new Employee("d", 37, 10000) ); Map<String, Employee> employeeMap1 = employeeList1.stream().collect(Collectors.toMap(Employee::getUsername, Function.identity(), (existingValue, newValue) -> newValue)); employeeMap1.forEach((k,v)-> System.out.println("key:"+k+" , value:"+v)); System.out.println("......................分隔............................."); //获得特定类型的Map 比如HashMap 和 TreeMap HashMap<String, Employee> employeeHashMap = employeeList1.stream().collect(Collectors.toMap(Employee::getUsername, Function.identity(), (existingValue, newValue) -> newValue, HashMap::new)); employeeHashMap.forEach((k,v)-> System.out.println("key:"+k+" , value:"+v)); }测试结果:
key:a , value:Employee{username=‘a’, age=20, salary=1000} key:b , value:Employee{username=‘b’, age=30, salary=3000} key:c , value:Employee{username=‘c’, age=35, salary=2500} key:d , value:Employee{username=‘d’, age=22, salary=7000} key:e , value:Employee{username=‘e’, age=37, salary=10000} …分隔… key:a , value:20 key:b , value:30 key:c , value:35 key:d , value:22 key:e , value:37 …分隔… key:a , value:Employee{username=‘a’, age=20, salary=1000} key:b , value:Employee{username=‘b’, age=30, salary=3000} key:c , value:Employee{username=‘c’, age=35, salary=2500} key:d , value:Employee{username=‘d’, age=37, salary=10000} …分隔… key:a , value:Employee{username=‘a’, age=20, salary=1000} key:b , value:Employee{username=‘b’, age=30, salary=3000} key:c , value:Employee{username=‘c’, age=35, salary=2500} key:d , value:Employee{username=‘d’, age=37, salary=10000}
群组和分区返回的值仍是Map,群组的键是根据提供的分组器返回的值进行确定,而分区的键就是True和False。直接见API
// 根据classifier 进行分组 public static <T, K> Collector<T, ?, Map<K, List<T>>>groupingBy(Function<? super T, ? extends K> classifier); // 根据classifier 进行分组,第一种方法返回的Value是List,downStream是为了修改Value的类型。比如直接返回元素的个数 public static <T, K, A, D> Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream); // 根据classifier 进行分组,第一种方法返回的Value是List,downStream是为了修改Value的类型。比如直接返回元素的个数.mapFactory是为了制定返回的Map类型。默认情况下是HashMap public static <T, K, D, A, M extends Map<K, D>> Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier, Supplier<M> mapFactory, Collector<? super T, A, D> downstream) ; // 根据predicate 进行分区 键为True 或 False public static <T>Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate); // 根据predicate 进行分区 键为True 或 False,第一种方法返回的Value是List,downStream是为了修改Value的类型。比如直接返回元素的个数 public static <T, D, A>Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate, Collector<? super T, A, D> downstream) ;测试代码:
@Test public void testGroupByAndPartition(){ List<Employee> employeeList = Arrays.asList( new Employee("a", 20, 1000), new Employee("b", 30, 3000), new Employee("c", 35, 2500), new Employee("d", 22, 7000), new Employee("e", 37, 10000) ); //按照年龄进行分组 Map<String, List<Employee>> listMap = employeeList.stream().collect(Collectors.groupingBy(x -> { if (x.getAge() < 30) return "Young"; else if (x.getAge() <= 35) return "Experienced"; else return "To_Be_Retired"; })); listMap.forEach((k,v)-> { System.out.print("K:"+k+" , "); System.out.print("V:"+v); System.out.println(); }); System.out.println("......................分隔............................."); Map<String, Long> map = employeeList.stream().collect(Collectors.groupingBy(x -> { if (x.getAge() < 30) return "Young"; else if (x.getAge() <= 35) return "Experienced"; else return "To_Be_Retired"; }, Collectors.counting())); map.forEach((k,v)-> { System.out.print("K:"+k+" , "); System.out.print("V:"+v); System.out.println(); }); System.out.println("......................分隔............................."); TreeMap<String, Long> map1 = employeeList.stream().collect(Collectors.groupingBy(x -> { if (x.getAge() < 30) return "Young"; else if (x.getAge() <= 35) return "Experienced"; else return "To_Be_Retired"; }, TreeMap::new, Collectors.counting())); map1.forEach((k,v)-> { System.out.print("K:"+k+" , "); System.out.print("V:"+v); System.out.println(); }); System.out.println("......................分隔............................."); // 以年龄30为分隔线,进行分区 Map<Boolean, List<Employee>> listMap1 = employeeList.stream().collect(Collectors.partitioningBy(x -> x.getAge() > 30)); listMap1.forEach((k,v)-> { System.out.print("K:"+k+" , "); System.out.print("V:"+v); System.out.println(); }); System.out.println("......................分隔............................."); Map<Boolean, Long> map2 = employeeList.stream().collect(Collectors.partitioningBy(x -> x.getAge() > 30, Collectors.counting())); map2.forEach((k,v)-> { System.out.print("K:"+k+" , "); System.out.print("V:"+v); System.out.println(); }); }测试结果:
K:Young , V:[Employee{username=‘a’, age=20, salary=1000}, Employee{username=‘d’, age=22, salary=7000}] K:Experienced , V:[Employee{username=‘b’, age=30, salary=3000}, Employee{username=‘c’, age=35, salary=2500}] K:To_Be_Retired , V:[Employee{username=‘e’, age=37, salary=10000}] …分隔… K:Young , V:2 K:Experienced , V:2 K:To_Be_Retired , V:1 …分隔… K:Experienced , V:2 K:To_Be_Retired , V:1 K:Young , V:2 …分隔… K:false , V:[Employee{username=‘a’, age=20, salary=1000}, Employee{username=‘b’, age=30, salary=3000}, Employee{username=‘d’, age=22, salary=7000}] K:true , V:[Employee{username=‘c’, age=35, salary=2500}, Employee{username=‘e’, age=37, salary=10000}] …分隔… K:false , V:3 K:true , V:2
关于群组和分区这一块其实比较复杂,因为涉及到了Collectors的一些方法调用。以后有时间再更新强化。
基本类型流和对象类型流有相似的地方,也有不同的地方。基础类型共分为8大类型,而基本类型流分为3大类型,IntStream用于存储 byte char short int boolean ,DoubleStream用于存储float,double,LongStream用于存储long。下面以IntStream作为例子进行讲解。讲解顺序为基本类型流的创建,基本类型流和其对应的包装流的转换,基本类型流特有的方法。
除了常规的创建方法以外,还可以通过如下方法创建:
// 产生元素 从 startInclusive 到 endExclusive(不含) 的流 public static IntStream range(int startInclusive, int endExclusive); // 产生元素 从 startInclusive 到 endInclusive(含) 的流 public static IntStream rangeClosed(int startInclusive, int endInclusive);测试代码:
@Test public void testCreateBasicTypeStream(){ IntStream intStream1 = IntStream.range(0, 3).skip(2); intStream1.forEach(System.out::println); System.out.println("..................分隔.................."); IntStream intStream2 = IntStream.rangeClosed(0, 3).skip(2); intStream2.forEach(System.out::println); }测试结果:
2 …分隔… 2 3
测试代码:
@Test public void testTransferBasicStream(){ int [] arr = {1,2,3}; IntStream intStream = Arrays.stream(arr); // IntStream 转为包装流 Stream<Integer> integerStream = intStream.boxed(); // 包装流 转为 intStream IntStream intStream1 = integerStream.mapToInt(Integer::intValue); }测试代码:
@Test public void testBasicStreamCommonMethods(){ int [] arr = {1,2,3}; //转为数组 int[] arr1 = Arrays.stream(arr).toArray(); //返回统计数据 该统计数据可以获得 最大值 最小值 平均值 元素个数 求和 IntSummaryStatistics intSummaryStatistics = Arrays.stream(arr).summaryStatistics(); System.out.println(intSummaryStatistics.getAverage()); //获得最小值 类似的有最大值 求和值 平均值 OptionalInt min = Arrays.stream(arr).min(); System.out.println(min.getAsInt()); }