赞
踩
Stream流——Java8新特性之一
用于处理集合,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。
Java Steam的操作是基于集合的。Steam的操作可以分为两种:中间操作和结束操作。Stream操作是延迟执行的。他会等到有结束操作的时候才执行中间操作链。
网上一般说它有三个特点:
不是数据结构,不会保存数据。
不会修改原来的数据源,它会将操作后的数据保存到另外一个对象中。(但是peek和map其实都能改变源数据)
惰性求值,流在中间处理过程中,只是对操作进行了记录,并不会立即执行,需要等到执行终止操作的时候才会进行实际的计算。
下面开始讲解:
使用到的实体类:
@Data
@AllArgsConstructor
@NoArgsConstructor
class Student {
private String name;
private int age;
}
@Data、@AllArgsConstructor、@NoArgsConstructor三个注解是在maven里面引用了lombok,作用是帮我们创建了get、set、有参与无参构造方法,也可以自己手动创建。
流的创建:
// 1、通过 Collection.stream() 方法用集合创建流 List<String> list = Arrays.asList("a", "b", "c"); // 创建一个顺序流 Stream<String> stream = list.stream(); // 创建一个并行流 Stream<String> parallelStream = list.parallelStream(); Stream<String> parallelStream2 = list.stream().parallel(); // 2、使用 Arrays.stream(T[] array) 方法用数组创建流 int[] array={1,3,5,6,8}; IntStream intStream = Arrays.stream(array); // 3、使用Stream的静态方法:of()、iterate()、generate() Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5, 6); stream1.forEach(System.out::println); Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 3).limit(10); stream2.forEach(System.out::println); Stream<Double> stream3 = Stream.generate(Math::random).limit(3); stream3.forEach(System.out::println);
filter:筛选
List<Integer> list = Arrays.asList(6, 7, 3, 8, 1, 2, 9);
Stream<Integer> stream = list.stream();
stream.filter(x -> x > 7).forEach(System.out::println);
concat:合并
String[] arr1 = {"a", "b", "c", "d"};
String[] arr2 = {"d", "e", "f", "g"};
Stream<String> stream1 = Stream.of(arr1);
Stream<String> stream2 = Stream.of(arr2);
List<String> newList = Stream.concat(stream1, stream2).collect(Collectors.toList());
System.out.println("流合并:" + newList);// 流合并:[a, b, c, d, d, e, f, g]
distinct:去重
Integer[] arr = {1, 2, 2, 3, 4, 4, 5};
List<Integer> list = Arrays.stream(arr).distinct().collect(Collectors.toList());
System.out.println("流去重:" + list);// 流去重:[1, 2, 3, 4, 5]
skip:跳过前n个数据
List<Integer> collect2 = Stream.iterate(1, x -> x + 2).skip(1).limit(5).collect(Collectors.toList());
System.out.println("skip:" + collect2);// skip:[3, 5, 7, 9, 11]
limit:获取前n个元素
List<Integer> collect = Stream.iterate(1, x -> x + 2).limit(10).collect(Collectors.toList());
System.out.println("limit:" + collect);// limit:[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
skip和limit加起来可以实现分页功能。
map:映射,函数会被应用到每个元素上,并将其映射成一个新的元素。
String[] strArr = {"rety", "fghfg", "terd", "nye"};
List<String> strList = Arrays.stream(strArr).map(String::toUpperCase).collect(Collectors.toList());
System.out.println("每个元素大写:" + strList);// 每个元素大写:[RETY, FGHFG, TERD, NYE]
//将每个元素转成一个新的且不带逗号的元素
List<String> list = Arrays.asList("h,y,g,g,e", "3,6,9,12");
List<String> listNew = list.stream().map(s -> s.replaceAll(",", "")).collect(Collectors.toList());
System.out.println("去掉逗号:" + listNew);// 去掉逗号:[hygge, 36912]
List<Integer> intList = Arrays.asList(2, 4, 5, 8, 10);
List<Integer> intListNew = intList.stream().map(x -> x + 2).collect(Collectors.toList());
System.out.println("每个元素+3:" + intListNew);// 每个元素+3:[4, 6, 7, 10, 12]
flatMap:映射,流中的每个值都换成另一个流,然后把所有流连接成一个流。
List<String> list = Arrays.asList("h,y,g,g,e", "3,6,9,12");
List<String> listNew = list.stream().flatMap(s -> {
//将每个元素转换成一个stream
String[] split = s.split(",");
Stream<String> s2 = Arrays.stream(split);
return s2;
}).collect(Collectors.toList());
System.out.println("处理前:元素个数为 " + list.size() + " 个,集合为 " + list);
System.out.println("处理后:元素个数为 " + listNew.size() + " 个,集合为 " + listNew);
/*
* 处理前:元素个数为 2 个,集合为 [h,y,g,g,e, 3,6,9,12]
* 处理后:元素个数为 9 个,集合为 [h, y, g, g, e, 3, 6, 9, 12]
*/
这里注意虽然他们的集合打印出来很相似,但是注意他们的元素个数是不一样的。
sorted:排序
// 默认排序:即无参时 List<Integer> list = Arrays.asList(5, 6, 4); list.stream().sorted().forEach(System.out::println); // 自定义排序 List<Student> studentList = new ArrayList<>(); studentList.add(new Student("A", 10)); studentList.add(new Student("B", 20)); studentList.add(new Student("A", 30)); studentList.add(new Student("D", 40)); // 按年龄升序 studentList.stream().sorted(Comparator.comparingInt(Student::getAge)).forEach(System.out::println); // 先按姓名升序,姓名相同则按年龄升序 studentList.stream().sorted( (o1, o2) -> { if (o1.getName().equals(o2.getName())) { return o1.getAge() - o2.getAge(); } else { return o1.getName().compareTo(o2.getName()); } } ).forEach(System.out::println); // 先按姓名升序,姓名相同则按年龄升序,更简单的写法 studentList.stream().sorted(Comparator.comparing(Student::getName).thenComparing(Student::getAge)).forEach(System.out::println);
peek:消费
List<Student> studentList = new ArrayList<>(); studentList.add(new Student("A", 10)); studentList.add(new Student("B", 20)); System.out.println("原始数据:" + studentList); // 使用map对比 List<Student> result = studentList.stream().map(o -> { o.setAge(50); return o; }).collect(Collectors.toList()); System.out.println("使用map修改数据:" + result); // 使用peek List<Student> result1 = studentList.stream().peek(o -> o.setAge(100)).collect(Collectors.toList()); System.out.println("使用peek修改数据:" + result1); /* * 原始数据:[Student(name=A, age=10), Student(name=B, age=20)] * 使用map修改数据:[Student(name=A, age=50), Student(name=B, age=50)] * 使用peek修改数据:[Student(name=A, age=100), Student(name=B, age=100)] */
这里专门使用了map与peek做了个对比,你会发现他们都改变了age的值,而peek区别与map,它不需要返回值。但是map能改变数据类型,peek就做不到。
所以peek 可以做一些打印或者修改工作。
源码注释中作者给的例子也证明了这种想法:
Stream.of("one", "two", "three", "four")
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e))
.collect(Collectors.toList());
对于map与peek的具体分析看这篇文章:——(待整理)——
max:返回流中元素的最大值
List<String> list = Arrays.asList("admn", "bgfmt", "pbtd", "xbafdd", "weoufgsd");
Optional<String> max = list.stream().max(Comparator.comparing(String::length));
System.out.println("最长的字符串:" + max.get());// 最长的字符串:weoufgsd
List<Integer> intlist = Arrays.asList(7, 6, 9, 4, 11, 6);
Optional<Integer> max2 = intlist.stream().max(Integer::compareTo);
System.out.println("自然排序的最大值:" + max2.get());// 自然排序的最大值:11
min:返回流中元素的最小值
List<String> list = Arrays.asList("admn", "bgfmt", "pbtd", "xbafdd", "weoufgsd");
Optional<String> min = list.stream().min(Comparator.comparing(String::length));
System.out.println("最短的字符串:" + min.get());// 最短的字符串:admn
List<Integer> intList = Arrays.asList(7, 6, 9, 4, 11, 6);
Optional<Integer> min2 = intList.stream().min(Integer::compareTo);
System.out.println("自然排序的最小值:" + min2.get());// 自然排序的最小值:4
count:返回流中元素的总个数
List<Integer> list = Arrays.asList(7, 6, 4, 8, 2, 11, 9);
long count = list.stream().count();
System.out.println("list的元素总个数:" + count);// list的元素总个数:7
long countOfGt6 = list.stream().filter(x -> x > 6).count();
System.out.println("list中大于6的元素个数:" + countOfGt6);// list中大于6的元素个数:4
allMatch:接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回true,否则返回false
List<Integer> list = Arrays.asList(7, 6, 9, 3, 8, 2, 1);
boolean allMatch = list.stream().allMatch(x -> x > 6);
System.out.println("是否全部值都大于6:" + allMatch);// 是否全部值都大于6:false
noneMatch:接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回true,否则返回false
List<Integer> list = Arrays.asList(7, 6, 9, 3, 8, 2, 1);
boolean noneMatch = list.stream().noneMatch(x -> x > 6);
System.out.println("是否不存在大于6的值:" + noneMatch);// 是否不存在大于6的值:false
anyMatch:接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回false
List<Integer> list = Arrays.asList(7, 6, 9, 3, 8, 2, 1);
boolean anyMatch = list.stream().anyMatch(x -> x > 6);
System.out.println("是否存在大于6的值:" + anyMatch);// 是否存在大于6的值:true
findFirst:返回流中第一个元素
List<Integer> list = Arrays.asList(7, 6, 9, 3, 8, 2, 1);
Optional<Integer> findFirst = list.stream().findFirst();
System.out.println("list中的第一个数:"+findFirst.get());// list中的第一个数:7
Optional<Integer> findFirstGt7 = list.stream().filter(x -> x > 7).findFirst();
System.out.println("list中第一个大于7的数:"+findFirstGt7.get());// list中第一个大于7的数:9
findAny:返回流中的任意元素
// 适用于并行流,源码有注释:This is to allow for maximal performance in parallel operations
List<Integer> list = Arrays.asList(7, 6, 9, 3, 8, 2, 1);
Optional<Integer> findAny = list.parallelStream().findAny();
System.out.println("匹配任意一个值:" + findAny.get());// 匹配任意一个值:8
Optional<Integer> findAnyGt5 = list.parallelStream().filter(x -> x > 5).findAny();
System.out.println("匹配任意一个大于5的值:" + findAnyGt5.get());// 匹配任意一个大于5的值:8
PS:使用上面代码自己测试的时候,得多次运行才可能得到不一样的返回值,可以试着改变list的长度或者类型来增加返回不同值的概率。
reduce:归约
例子中也可以使用(x, y) -> x > y ? x : y 替换 Integer::max
List<Integer> list = Arrays.asList(1, 3, 2, 8, 11, 4); // 一个参数:Optional<T> reduce(BinaryOperator<T> accumulator) Optional<Integer> sum = list.stream().reduce(Integer::sum); Optional<Integer> product = list.stream().reduce((x, y) -> x * y); Optional<Integer> max = list.stream().reduce(Integer::max); System.out.println("list求和:" + sum.get()+ ",求积:" + product.get()+",最大值:" + max.get());// list求和:29,求积:2112,最大值:11 // 两个参数:T reduce(T identity, BinaryOperator<T> accumulator) Integer sum2 = list.stream().reduce(0, Integer::sum); Integer product2 = list.stream().reduce(1, (x, y) -> x * y); Integer max2 = list.stream().reduce(0, Integer::max); System.out.println("list求和:" + sum2+ ",求积:" + product2+",最大值:" + max2);// list求和:29,求积:2112,最大值:11 // <U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner) Integer sum3 = list.parallelStream().reduce(0, Integer::sum, Integer::sum); Integer product3 = list.stream().reduce(1, (x, y) -> x * y, (x, y) -> x * y); Integer max3 = list.stream().reduce(0, Integer::max, Integer::max); System.out.println("list求和:" + sum3+ ",求积:" + product3+",最大值:" + max3);// list求和:29,求积:2112,最大值:11
reduce三种不同方式的分析,请看这篇文章:——(待整理)——
collect:归集 (Collectors:toList/toSet/toMap)
List<Student> students = new ArrayList<>(); students.add(new Student("A", 10)); students.add(new Student("B", 20)); students.add(new Student("C", 10)); // 转成list:toList List<Integer> ageList = students.stream().map(Student::getAge).collect(Collectors.toList()); System.out.println("ageList:" + ageList);// ageList:[10, 20, 10] // 转成Set:toSet Set<Integer> ageSet = students.stream().map(Student::getAge).collect(Collectors.toSet()); System.out.println("ageSet:" + ageSet);// ageSet:[20, 10] // 类似Set:distinct去重 List<Integer> ageSet1 = students.stream().map(Student::getAge).distinct().collect(Collectors.toList()); System.out.println("ageSet1:" + ageSet1);// ageSet1:[10, 20] // 转成Map:toMap Map<String, Integer> studentMap = students.stream().collect(Collectors.toMap(Student::getName, Student::getAge)); System.out.println("studentMap:" + studentMap);// studentMap:{A=10, B=20, C=10}
使用collect(Collectors.toSet())与distinct().collect(Collectors.toList()),最终都能得到一个包含不重复元素的集合,但是你会发现他们元素的顺序不一样,可以大胆的猜测他们写入的顺序不一样,具体分析请看文章:——(待整理)——
上面基本上列举出了常用的方法,面对实际问题,我们使用Stream时,可以采用非常多的方式来达到目的。下面我们用一个例子以及一些问题,尽可能的给出多种解决方案,来让大家来熟悉这些方法。
用到的实体类:
@Data
@AllArgsConstructor
@NoArgsConstructor
class Person {
private String name; // 姓名
private int salary; // 薪资
private int age; // 年龄
private String sex; // 性别
private String area; // 地区
}
实例化:
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("张三", 39000, 23, "男", "北京"));
personList.add(new Person("王五", 20000, 25, "男", "上海"));
personList.add(new Person("李四", 28000, 21, "女", "上海"));
personList.add(new Person("小明", 32000, 24, "女", "北京"));
personList.add(new Person("小刚", 45000, 25, "男", "北京"));
personList.add(new Person("小红", 29000, 26, "女", "上海"));
1、工资大于28000的人员列表
Map<String, Integer> map = personList.stream().filter(p -> p.getSalary() > 28000).collect(Collectors.toMap(Person::getName, Person::getSalary));
System.out.println("高于28000的员工姓名:" + map.keySet());
List<String> List = personList.stream().filter(x -> x.getSalary() > 28000).map(Person::getName).collect(Collectors.toList());
System.out.println("高于28000的员工姓名:" + List);
/*
* 高于28000的员工姓名:[小刚, 张三, 小明, 小红]
* 高于28000的员工姓名:[张三, 小明, 小刚, 小红]
*/
2、改变员工信息
// 不改变原来员工集合的方式 List<Person> personListNew = personList.stream().map(person -> { Person personNew = new Person(person.getName(), 0, 0, null, null); personNew.setSalary(person.getSalary() + 10000); return personNew; }).collect(Collectors.toList()); System.out.println("一次改动前:" + personList); System.out.println("一次改动后:" + personListNew); // 改变原来员工集合的方式 List<Person> personListNew2 = personList.stream().map(person -> { person.setSalary(person.getSalary() + 10000); return person; }).collect(Collectors.toList()); System.out.println("二次改动前:" + personList); System.out.println("二次改动后:" + personListNew2);
3、求工资之和方式:
Optional<Integer> sumSalary = personList.stream().map(Person::getSalary).reduce(Integer::sum); Integer sumSalary2 = personList.stream().map(Person::getSalary).reduce(0, Integer::sum); Integer sumSalary3 = personList.parallelStream().reduce(0, (sum, p) -> sum += p.getSalary(), Integer::sum); OptionalInt sumSalary4 = personList.stream().mapToInt(Person::getSalary).reduce(Integer::sum); int sumSalary5 = personList.stream().mapToInt(Person::getSalary).sum(); Integer sumSalary6 = personList.stream().collect(Collectors.summingInt(Person::getSalary)); System.out.println("工资之和:" + sumSalary.get() + "," + sumSalary2 + "," + sumSalary3 + "," + sumSalary4.getAsInt() + "," + sumSalary5 + "," + sumSalary6); /* * 工资之和:193000,193000,193000,193000,193000,193000 */
4、求最高工资方式:
Optional<Integer> maxSalary = personList.stream().map(Person::getSalary).reduce((max1, max2) -> max1 > max2 ? max1 : max2); Optional<Integer> maxSalary2 = personList.stream().map(Person::getSalary).reduce(Integer::max); Integer maxSalary3 = personList.stream().map(Person::getSalary).reduce(0, Integer::max); Integer maxSalary4 = personList.parallelStream().reduce(0, (max, p) -> max > p.getSalary() ? max : p.getSalary(), Integer::max); Optional<Integer> maxSalary5 = personList.stream().map(Person::getSalary).collect(Collectors.maxBy(Integer::compare)); Optional<Integer> maxSalary6 = personList.stream().map(Person::getSalary).max(Integer::compare); Optional<Person> maxSalary7 = personList.stream().max(Comparator.comparingInt(Person::getSalary)); System.out.println("最高工资:" + maxSalary.get() + "," + maxSalary2.get() + "," + maxSalary3 + "," + maxSalary4 + "," + maxSalary5.get() + "," + maxSalary6.get() + "," + maxSalary7.get().getSalary()); /* * 最高工资:45000,45000,45000,45000,45000,45000,45000 */
5、求平均工资
Double average = personList.stream().collect(Collectors.averagingDouble(Person::getSalary));
Double average2 = personList.stream().mapToDouble(Person::getSalary).sum() / personList.size();
System.out.println("员工平均工资:" + average + "," + average2);
/*
* 员工平均工资:32166.666666666668,32166.666666666668
*/
6、求人员总数
long count = personList.stream().collect(Collectors.counting());
long count2 = personList.stream().count();
long count3 = personList.size();
System.out.println("员工总数:" + count + "," + count2 + "," + count3);
/*
* 员工总数:6,6,6
*/
7、一次性统计所有信息
DoubleSummaryStatistics collect = personList.stream().collect(Collectors.summarizingDouble(Person::getSalary));
System.out.println("员工工资所有统计:" + collect);
/*
* 员工工资所有统计:DoubleSummaryStatistics{count=6, sum=193000.000000, min=20000.000000, average=32166.666667, max=45000.000000}
*/
8、分组
// 将员工按薪资是否高于8000分组
Map<Boolean, List<Person>> groupBySalary = personList.stream().collect(Collectors.partitioningBy(x -> x.getSalary() > 8000));
System.out.println("按员工薪资是否大于8000分组情况:" + groupBySalary);
// 将员工按性别分组
Map<String, List<Person>> groupBySex = personList.stream().collect(Collectors.groupingBy(Person::getSex));
System.out.println("按员工性别分组情况:" + groupBySex);
// 将员工先按性别分组,再按地区分组
Map<String, Map<String, List<Person>>> group = personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getArea)));
System.out.println("按员工性别、地区:" + group);
9、排序
// 按工资升序排序(自然排序) List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName) .collect(Collectors.toList()); System.out.println("按工资升序排序:" + newList); // 按工资倒序排序 List<String> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed()) .map(Person::getName).collect(Collectors.toList()); System.out.println("按工资降序排序:" + newList2); // 先按工资再按年龄升序排序 List<String> newList3 = personList.stream() .sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).map(Person::getName) .collect(Collectors.toList()); System.out.println("先按工资再按年龄升序排序:" + newList3); // 先按工资再按年龄自定义排序(降序) List<String> newList4 = personList.stream().sorted((p1, p2) -> { if (p1.getSalary() == p2.getSalary()) { return p2.getAge() - p1.getAge(); } else { return p2.getSalary() - p1.getSalary(); } }).map(Person::getName).collect(Collectors.toList()); System.out.println("先按工资再按年龄自定义降序排序:" + newList4); /* * 按工资升序排序:[王五, 李四, 小红, 小明, 张三, 小刚] * 按工资降序排序:[小刚, 张三, 小明, 小红, 李四, 王五] * 先按工资再按年龄升序排序:[王五, 李四, 小红, 小明, 张三, 小刚] * 先按工资再按年龄自定义降序排序:[小刚, 张三, 小明, 小红, 李四, 王五] */
上面有些方法其实使用并不恰当,在这里只是起到了抛砖引玉的作用,大家选取最适用的的即可。
—————————————————————————————————————————————
与Spark RDD算子比较。
其实如果你有使用Spark的经验的话,那么你肯定和我一样,使用Java Stream时,会有一种似曾相识的感觉。
Spark API 的所有操作都是基于RDD的。对RDD进行函数操作分为两种:Transformation和Action。Spark在遇到Transformation操作时只会记录需要这样的操作,并不会去执行,需要等到有Action操作的时候才会真正启动计算过程进行计算。
注:RDD俗称弹性分布式数据集,数据不只存储在一台机器上,而是分布在多台机器上,实现数据计算的并行化。
你可以发现这个描述和文章开始对Java Steam的描述,过程极其相似。他们的方法也有很多相似的,大家可以做个对比。
Transformation算子:map()、filter()、flatMap()、sample()、union()、groupByKey()、reduceByKey( )、join()
Actions算子:reduce()、collect()、count()、take(n)、first()、saveAsTextFile()、foreach()、saveAsSequenceFile()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。