将对要处理的集合当做数据源,看作一种数据流,在流的过程中,借助stream的API对流中的元素进行处理:过滤,排序等;
每次返回新的流,可以多次调用中间操作。中间操作不会对源数据产生影响。
每个流只会进行一次终端操作,终端操作后流就无法使用了。终端操作结束后会产生新的集合或值。
Stream不存储数据,只对数据按照一定的操作进行处理,再输出结果;
Stream不改变数据集,终端操作后生成新的数据集或值;
Stream具有延迟性,只有调用终端操作时,中间操作才会进行。
List<String> list = Arrays.asList("a","b","c"); // 创建顺序流 Stream<String> stream = list.stream(); // 创建并行流 Stream<String> stringStream = list.parallelStream();
顺序流与顺序流的简单区别
stream() 顺序流:主线程对多个流进行顺序执行
parallelStream() 并发流:内部以多线程的形式对流进行并发执行
当数据量足够大的情况下,并发流可以加快速度,但是前提一定是流中的数据没有顺序要求。
除了直接创建并发流以外,还提供了把顺序流转为并发流:
Stream<String> parallel = list.stream().parallel();
String[] strings = {"1","2","3","4"}; Stream<String> stream = Arrays.stream(strings); int[] array = {1, 2, 3}; // 数组创建流 IntStream stream = Arrays.stream(array);
Stream<String> stream = Stream.of("1","2","3");
Stream<String> stream = Stream.generate(() ->{return UUID.randomUUID(); });
generate()里面的参数是一个Supplier(供给型)类型的函数式接口
生成无限长度的Stream,和generator不同的是,其元素的生成是重复对给定的种子值(seed)调用用户指定函数来生成的
Stream.iterate(0, (x) -> { return x + 3; }).limit(5).forEach((integer) ->{ System.out.println(integer); }); //结果是0,3,6,9,12
先创建一个数据源实体类
@Data public class Person { /** * 姓名 */ private String name; /** * 薪资 */ private int salary; /** * 年龄 */ private int age; /** * 性别 */ private String sex; /** * 地区 */ private String area; public Person(String name, int salary, int age, String sex, String area) { this.name = name; this.salary = salary; this.age = age; this.sex = sex; this.area = area; } }
创建数据源
List<Person> list = new ArrayList<>(); list.add(new Person("Tom", 8900, 25,"male", "New York")); list.add(new Person("Jack", 7000, 26,"male", "Washington")); list.add(new Person("Lily", 7800, 27,"female", "Washington")); list.add(new Person("Anni", 8200, 28,"female", "New York")); list.add(new Person("Owen", 9500, 29,"male", "New York")); list.add(new Person("Alisa", 7900, 30,"female", "New York"));
//遍历每一个元素,并打印元素的姓名 list.stream().forEach(person -> { System.out.println(person.getName()); }); //查找到第一个,并打印出他的姓名 Optional<Person> first = list.stream().findFirst(); //findAny是查找到一个立马返回查到的,适用于并发流 System.out.println(first.get().getName()); //是否匹配到,任意匹配 boolean jack = list.stream().anyMatch(person -> { return person.getName().equals("Jack"); }); System.out.println(jack);
Tom Jack Lily Anni Owen Alisa Tom Tom true
//查出年龄最大的那个人 Optional<Person> max = list.stream().max(((o1, o2) -> { return o1.getAge() - o2.getAge(); })); System.out.println("年龄最大的是:"+max.get().getName()+"他的年龄是:"+max.get().getAge()); //查出年龄最小的那个人,需要注意的比较器的方法还是一样的 o1.getAge() - o2.getAge() Optional<Person> min = list.stream().min((o1, o2) -> { return o1.getAge() - o2.getAge(); }); System.out.println("年龄最小的是:"+min.get().getName()+"他的年龄是:"+min.get().getAge()); //统计数据源的元素个数 long count = list.stream().count(); System.out.println(count); //统计工资高于8000的元素个数 long count1 = list.stream().filter((person) -> { return person.getSalary() > 8000; }).count(); System.out.println(count1);
年龄最大的是:Alisa他的年龄是:30 年龄最小的是:Tom他的年龄是:25 6 3
Optional<Integer> reduce = list.stream().map((person) -> { return person.getSalary(); }).reduce((integer1, integer2) -> { return integer1 + integer2; }); System.out.println(reduce.get());
49300
map操作里面接收的是一个功能性函数式接口,接收一个类型值,返回另一个类型的,
比如接收了Person,返回的是薪水person.getSalary()。然后将这些薪水当成新的数据源再进行reduce操作或其他操作。
//筛选出年龄大于26的并统计人数 long count2 = list.stream().filter((person -> { return person.getAge() > 26; })).count(); System.out.println(count2);
//将元素的姓名收集到一个新的集合中并打印出来 List<String> names = new ArrayList<>(); names = list.stream().map(person -> {return person.getName();}).collect(Collectors.toList()); System.out.println(names);
flatMap操作使用里一个例子
String[] words = new String[]{"Hello", "World"}; Gson gson = new Gson(); //这是map操作 List<String[]> mapList = Stream.of(words).map(w -> w.split("")).distinct().collect(Collectors.toList()); System.out.println(gson.toJson(mapList)); 结果:[["H","e","l","l","o"],["W","o","r","l","d"]]
//这是flatMap操作 List<String> flatMapCollect = Stream.of(words).flatMap(w -> Stream.of(w.split(""))).distinct().collect(Collectors.toList()); System.out.println(gson.toJson(flatMapCollect)); 结果:["H","e","l","o","W","r","d"]
collect,收集是内容最多,功能最丰富的部分。字面理解就是将一个流收集起来,最终可以收集成一个值也可以是一个新的集合。主要依赖java.util.stream.Collectors类内置的静态方法。
归集(toList/toSet/toMap)
因为流不存储数据,所以流处理完成后,需要一个容器去收集。toList、toSet和toMap比较常用,另外还有toCollection、toConcurrentMap等复杂一些的用法。
toList:
List<String> names = new ArrayList<>(); names = list.stream().map(person -> {return person.getName();}).collect(Collectors.toList()); //将姓名重新收集到一个List中去 System.out.println(names);
[Tom, Jack, Lily, Anni, Owen, Alisa]
toMap:
Map<String, String> map = list.stream().collect(Collectors.toMap(person -> person.getName(), person -> person.getArea())); //以姓名为key,地址为value的map System.out.println(map);
{Tom=New York, Owen=New York, Anni=New York, Alisa=New York, Jack=Washington, Lily=Washington}
toSet:
Set<Person> set = list.stream().collect(Collectors.toSet());//可以直接收集 System.out.println(set);
[Person(name=Anni, salary=8200, age=28, sex=female, area=New York), Person(name=Lily, salary=7800, age=27, sex=female, area=Washington), Person(name=Tom, salary=8900, age=25, sex=male, area=New York), Person(name=Jack, salary=7000, age=26, sex=male, area=Washington), Person(name=Owen, salary=9500, age=29, sex=male, area=New York), Person(name=Alisa, salary=7900, age=30, sex=female, area=New York)]
统计(count/averaging)
count:
count之前用过很多次了,就是用来计数啥的,比如统计员工数、加上中间操作map,统计工资总计等等;
averaging:
平均值,平均值还有averagingLong、averagingDouble等
//统计平均工资 Double averagSalary = list.stream().collect(Collectors.averagingInt(person -> person.getSalary())); System.out.println(averagSalary);
8216.666666666666
最值(maxBy\minBy)
maxBy:
//注意返回的是Person类型的Optional Optional<Person> maxSalary = list.stream().collect(Collectors.maxBy((o1, o2) -> o1.getSalary() - o2.getSalary())); System.out.println(maxSalary.get().getSalary());
9500
minBy:
和maxBy一样的。
求和(summingInt\summingLong\summingDouble)
summingInt:
//很简单的操作 Integer sumSalary = list.stream().collect(Collectors.summingInt(person -> person.getSalary())); System.out.println(sumSalary);
summarizingInt:
summarizingInt是所有维度汇总,就是总共几个人,最大,最小,平均啥的,
IntSummaryStatistics sumSalary = list.stream().collect(Collectors.summarizingInt(person -> person.getSalary())); System.out.println(sumSalary);
IntSummaryStatistics{count=6, sum=49300, min=7000, average=8216.666667, max=9500}
分组(partitioningBy/groupingBy)
partitioningBy:
方法参数为Predicate接口,该接口返回值是boolean类型,所以partitioningBy也只能将集合按true/false分为两组。
//按照工资是否大于8000分为两组 Map<Boolean, List<Person>> booleanListMap = list.stream().collect(Collectors.partitioningBy(person -> person.getSalary() > 8000)); System.out.println(booleanListMap);
{ false=[Person(name=Jack, salary=7000, age=26, sex=male, area=Washington), Person(name=Lily, salary=7800, age=27, sex=female, area=Washington), Person(name=Alisa, salary=7900, age=30, sex=female, area=New York)], true=[Person(name=Tom, salary=8900, age=25, sex=male, area=New York), Person(name=Anni, salary=8200, age=28, sex=female, area=New York), Person(name=Owen, salary=9500, age=29, sex=male, area=New York)] }
groupingBy:
方法参数为Function接口,key是泛型,所以groupingBy可以将集合分为多组。
//薪水为key,也可以以姓名为key,那就是person.getName(),map的value是类型为Person的List Map<Integer, List<Person>> collect = list.stream().collect(Collectors.groupingBy(person -> person.getSalary())); System.out.println(collect);
{ 8900=[Person(name=Tom, salary=8900, age=25, sex=male, area=New York)], 8200=[Person(name=Anni, salary=8200, age=28, sex=female, area=New York)], 7800=[Person(name=Lily, salary=7800, age=27, sex=female, area=Washington)], 7000=[Person(name=Jack, salary=7000, age=26, sex=male, area=Washington)], 7900=[Person(name=Alisa, salary=7900, age=30, sex=female, area=New York)], 9500=[Person(name=Owen, salary=9500, age=29, sex=male, area=New York)] }
接合(joining)
joining可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。
//我试了几次,String才能join String str = list.stream().map(person -> person.getName()).collect(Collectors.joining(",")); System.out.println(str);
Tom,Jack,Lily,Anni,Owen,Alisa
归约(reducing)
Collectors类提供的reducing方法,相比于stream本身的reduce方法,增加了对自定义归约的支持。
// 汇总扣除起征点后工资(Collectors类提供的reducing) Integer integer = list.stream().collect(Collectors.reducing(0, person -> person.getSalary(), (integer1, integer2) -> integer1 + integer2 - 5000)); System.out.println(integer);
说明一下:integer1一开始是第一个参数的0,integer2 是第一个元素的薪水8900,0 + 8900 - 5000 = 3900,这个运算结果就是下一次的integer1的值。
19300
排序(sorted)
sorted():自然排序,流中元素类型需实现Comparable接口;
sorted(Comparator com):Comparator排序器自定义排序;
自然排序:
// 工资顺序排序 List<String> salaryAse = list.stream() .sorted(Comparator.comparing(person -> person.getSalary())) .map(person -> person.getName()).collect(Collectors.toList()); System.out.println(salaryAse); // 工资倒序排序 List<String> salaryDesc = list.stream() .sorted(Comparator.comparing((Person person) -> person.getSalary()).reversed()) .map(person -> person.getName()).collect(Collectors.toList()); System.out.println(salaryAse);
[Jack, Lily, Alisa, Anni, Tom, Owen] [Owen, Tom, Anni, Alisa, Lily, Jack]
注意:Comparator.comparing((Person person) -> person.getSalary()).reversed(),要显示注明类型,不然编译推断不出类型就当做Object了。
排序是对null值的处理:
// 工资顺序排序,空值在前 List<String> nullFirst = list.stream().sorted(Comparator.comparing(person -> person.getSalary() ,Comparator.nullsFirst((o1, o2) -> o1.compareTo(o2)))).map(person -> person.getName()).collect(Collectors.toList()); ///工资顺序排序,空值在后 List<String> nullLast = list.stream().sorted(Comparator.comparing(person -> person.getSalary() ,Comparator.nullsFirst((o1, o2) -> o1.compareTo(o2)))).map(person -> person.getName()).collect(Collectors.toList());
说明:o1 ,o2就是薪水。
组合排序:
// 先工资倒序,再年龄正序 List<String> thenComparingStr = list.stream().sorted(Comparator.comparing((Person person) -> person.getSalary(), Comparator.reverseOrder()).thenComparing(person -> person.getAge(), Comparator.naturalOrder())) .map(person -> person.getName()).collect(Collectors.toList()); System.out.println(thenComparingStr);
//这里结果和工资倒序的结果是一样的,参考Sql [Owen, Tom, Anni, Alisa, Lily, Jack]
提取/组合
去重(distinct):
// 聚合员工所有地区并去重 List<String> distinctStr = list.stream().map(person -> person.getArea()).distinct().collect(Collectors.toList()); System.out.println(distinctStr);
[New York, Washington]
限制(limit):
// 所有员工按收入高低排序后取前三 List<String> limitStr = list.stream() .sorted(Comparator.comparing((Person person) -> person.getSalary(), Comparator.reverseOrder())).limit(3) .map(person -> person.getName()).collect(Collectors.toList()); System.out.println(limitStr);
[Owen, Tom, Anni]
跳过(skip):
// 所有员工按收入高低排序后跳过最高的 List<String> skipStr = list.stream() .sorted(Comparator.comparing((Person person) -> person.getSalary(), Comparator.reverseOrder())).skip(1) .map(person -> person.getName()).collect(Collectors.toList()); System.out.println(skipStr);
[Tom, Anni, Alisa, Lily, Jack]
skip + limit 实现分页:
// 分页,从3个数据开始,每页展示2条 int begin = 2; int size = 2; List<String> paginationStr = list.stream() .sorted(Comparator.comparing((Person person) -> person.getSalary(), Comparator.reverseOrder())).skip(begin).limit(size) .map(person -> person.getName()).collect(Collectors.toList()); System.out.println(paginationStr);
[Anni, Alisa]