Java Stream流的使用

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 reduce(BinaryOperator 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 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 reduce(U identity,BiFunction accumulator,BinaryOperator 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()

你可能感兴趣的:(java,java,maven,开发语言)