Java8新特性Stream的常见用法

1,025 阅读8分钟

这是我参与8月更文挑战的第2天

Stream简介

Stream流是java 8 中新引入的特性,用来处理集合中的数据,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。

Stream不是数据结构,也不保存数据,它是有关算法和计算的,更像一个高级版本的迭代器Iterator。原始版本的 Iterator,只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“判断是否包含某个字符”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。

Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,可以很方便地写出高性能的并发程序。

Stream的使用

流操作的类型有三种:

(1)创建流

(2)修改流元素(中间操作,Intermediate Operations)

(3)消费流元素(终端操作,Terminal Operations)

创建流 流有两种:(1)stream() : 创建串行流。(2)parallelStream() : 创建并行流。

并行流的特点就是将一个大任务切分成多个小任务,无序一起执行,当然如果我们需要顺序输出的话可以使用forEachOrdered,速度会比串行流快一些。它通过默认的ForkJoinPool,可能提高你的多线程任务的速度。

(1) 通过Stream.of()将元素转化成流

//Stream.of创建流

Stream stream = Stream.of("你", "我", "她");

(2)每个集合都可以通过调用 stream() 方法来产生一个流

String [] strArray = new String[] {"a", "b", "c"};

stream = Stream.of(strArray);

stream = Arrays.stream(strArray);

List list = Arrays.asList(strArray);

stream = list.stream();

Set w = new HashSet<>(Arrays.asList(strArray))

stream = w.stream();

使用举例

案例里使用的类

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;
        }
 
        public String getName() {
            return name;
        }
 
        public void setName(String name) {
            this.name = name;
        }
 
        public int getSalary() {
            return salary;
        }
 
        public void setSalary(int salary) {
            this.salary = salary;
        }
 
        public int getAge() {
            return age;
        }
 
        public void setAge(int age) {
            this.age = age;
        }
 
        public String getSex() {
            return sex;
        }
 
        public void setSex(String sex) {
            this.sex = sex;
        }
 
        public String getArea() {
            return area;
        }
 
        public void setArea(String area) {
            this.area = area;
        }
 
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", salary=" + salary +
                    ", age=" + age +
                    ", sex='" + sex + '\'' +
                    ", area='" + area + '\'' +
                    '}';
        }
    }

(1) 遍历/匹配(foreach/find/match)

Stream也是支持类似集合的遍历和匹配元素的,只是Stream中的元素是以Optional类型存在的。Optional类是一个可以为null的容器对象,调用get()方法会返回该对象。

@Test
public void streamTest1() {
    List<Person> personList = new ArrayList<Person>();
    personList.add(new Person("鸣人", 8900, 18, "1","螺旋丸"));
    personList.add(new Person("佐助", 8800, 18, "1","须佐能乎"));
    personList.add(new Person("小樱", 7800, 17, "2","治疗术"));
    personList.add(new Person("自来也", 8200, 30, "1","通灵术"));
    personList.add(new Person("大蛇丸", 9500, 30, "1","八岐大蛇"));
    personList.add(new Person("纲手", 7900, 29, "2","百豪之术"));

    // 遍历输出符合条件的对象, 战斗力大于8000
    personList.stream().filter(person -> person.getSalary() > 8000).forEach(System.out::println);
    // 匹配第一个
    Optional<Person> findFirst = personList.stream().filter(person -> person.getAge() > 18).findFirst();
    System.out.println("匹配第一个值:" + findFirst.get());
    // 匹配随机一个(适用于并行流)
    Optional<Person> findAny = personList.parallelStream().filter(person -> person.getAge() > 18).findAny();
    System.out.println("匹配随机一个值:" + findAny.get());
    // 是否包含符合特定条件  年龄大于20
    boolean anyMatch = personList.stream().anyMatch(person -> person.getAge() > 20);
    System.out.println("是否存在年龄大于20的:" + anyMatch);
}

运行结果:

(2)筛选(filter)

筛选,是按照一定的规则校验流中的元素,将符合条件的元素提取到新的流中的操作。

    public void streamTest2() {
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("鸣人", 8900, 18, "1","螺旋丸"));
        personList.add(new Person("佐助", 8800, 18, "1","须佐能乎"));
        personList.add(new Person("小樱", 7800, 17, "2","治疗术"));
        personList.add(new Person("自来也", 8200, 30, "1","通灵术"));
        personList.add(new Person("大蛇丸", 9500, 30, "1","八岐大蛇"));
        personList.add(new Person("纲手", 7900, 29, "2","百豪之术"));
    //筛选出所有战斗力大于8000的
    List<Person> list =  personList.stream().filter(person -> person.getSalary() > 8000).collect(Collectors.toList());
    System.out.println("战斗力大于8000的值:" + list);
}

运行结果:

(3)聚合(max/min/count/sum)

max、min、count、sum这些字眼你一定不陌生,没错,在mysql中我们常用它们进行数据统计。Java stream中也引入了这些概念和用法,极大地方便了我们对集合、数组的数据统计工作。

@Test
public void streamTest3() {
    List<Person> personList = new ArrayList<Person>();
    personList.add(new Person("鸣人", 8900, 18, "1","螺旋丸"));
    personList.add(new Person("佐助", 8800, 18, "1","须佐能乎"));
    personList.add(new Person("小樱", 7800, 17, "2","治疗术"));
    personList.add(new Person("自来也", 8200, 30, "1","通灵术"));
    personList.add(new Person("大蛇丸", 9500, 31, "1","八岐大蛇"));
    personList.add(new Person("纲手", 7900, 29, "2","百豪之术"));

    //获取年龄最大的
    Optional<Person> max = personList.stream().max(Comparator.comparing(Person::getAge));
    System.out.println("年龄最大的是:" + max.get());

    //获取战斗力最小的
    Optional<Person> min = personList.stream().min(Comparator.comparing(Person::getSalary));
    System.out.println("战斗力最小的是:" + min.get());

    //计算战斗力大于8000的有几个人
    long count = personList.stream().filter(person -> person.getSalary() > 8000).count();
    System.out.println("战斗力大于8000的人数是:" + count);

    //计算总年龄是多少
    int sum = personList.stream().mapToInt(person -> person.getAge()).sum();
    System.out.println("总年龄是是:" + sum);
}

运行结果:

(4)排序(sorted)

stream中有两种排序:

sorted():自然排序,流中元素需实现Comparable接口

sorted(Comparator com):自定义排序,自定义Comparator排序器

@Test
public void streamTest4() {
    List<Person> personList = new ArrayList<Person>();
    personList.add(new Person("鸣人", 8900, 18, "1","螺旋丸"));
    personList.add(new Person("佐助", 8800, 18, "1","须佐能乎"));
    personList.add(new Person("小樱", 7800, 17, "2","治疗术"));
    personList.add(new Person("自来也", 8200, 30, "1","通灵术"));
    personList.add(new Person("大蛇丸", 9500, 31, "1","八岐大蛇"));
    personList.add(new Person("纲手", 7900, 29, "2","百豪之术"));

    //按战斗力升序排序(自然排序)
    List<Person> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).collect(Collectors.toList());
    System.out.println("按战斗力升序排序:" + newList);

    //按战斗力降序排序(自然排序)
    List<Person> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed()).collect(Collectors.toList());
    System.out.println("按战斗力降序排序:" + newList2);

    // 先按年龄再按战斗力排序(自定义排序 降序)
    List<Person> newList3 = personList.stream().sorted((p1, p2) -> {
        if (p1.getAge() == p2.getAge()) {
            return p2.getSalary() - p1.getSalary();
        } else {
            return p2.getAge() - p1.getAge();
        }
    }).collect(Collectors.toList());
    System.out.println("先按年龄再按战斗力:" + newList3);

}

运行结果:

(5) 映射(map/flatMap)

映射,可以将一个流的元素按照一定的映射规则映射到另一个流中。分为map和flatMap:

map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。

flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。

    public void streamTest5() {
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("鸣人", 8900, 18, "1","螺旋丸"));
        personList.add(new Person("佐助", 8800, 18, "1","须佐能乎"));
        personList.add(new Person("小樱", 7800, 17, "2","治疗术"));
        personList.add(new Person("自来也", 8200, 30, "1","通灵术"));
        personList.add(new Person("大蛇丸", 9500, 31, "1","八岐大蛇"));
        personList.add(new Person("纲手", 7900, 29, "2","百豪之术"));
    //将人物和必杀技组合,
    List<String> strList = personList.stream().map(person -> person.getName() + "-->" + person.getArea()).collect(Collectors.toList());
    System.out.println("人物和必杀技组合:" + strList);
    
}

运行结果:

(6)归约(reduce)

归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。

@Test
public void streamTest6() {
    List<Person> personList = new ArrayList<Person>();
    personList.add(new Person("鸣人", 8900, 18, "1","螺旋丸"));
    personList.add(new Person("佐助", 8800, 18, "1","须佐能乎"));
    personList.add(new Person("小樱", 7800, 17, "2","治疗术"));
    personList.add(new Person("自来也", 8200, 30, "1","通灵术"));
    personList.add(new Person("大蛇丸", 9500, 31, "1","八岐大蛇"));
    personList.add(new Person("纲手", 7900, 29, "2","百豪之术"));

    //求战斗力总和 方式1
    Integer sum = personList.stream().map(Person::getSalary).reduce((x,y) -> x + y).get();
    System.out.println("战斗力总和(方式1):" + sum);
    //求战斗力总和 方式2
    Integer sum2 = personList.stream().map(Person::getSalary).reduce(Integer::sum).get();
    System.out.println("战斗力总和(方式2):" + sum2);
    //求战斗力总和 方式3
    Integer sum3 = personList.stream().map(Person::getSalary).reduce(0,Integer::sum);
    System.out.println("战斗力总和(方式3):" + sum3);
    //求年龄的乘积
    Integer product  = personList.stream().map(Person::getAge).reduce((x,y) -> x * y).get();
    System.out.println("年龄的乘积:" + product);
    //求战斗力最大的值
    Integer max  = personList.stream().map(Person::getSalary).reduce(1,Integer::max);
    System.out.println("斗力最大的值:" + max);
}

运行结果:

(7)收集(collect)

收集,可以说是内容最繁多、功能最丰富的部分了。从字面上去理解,就是把一个流收集起来,最终可以是收集成一个值也可以收集成一个新的集合。主要依赖java.util.stream.Collectors类内置的静态方法。

(8)归集(toList/toSet/toMap)

因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。toList、toSet和toMap是比较常见的用法。

@Test
public void streamTest7() {
    List<Person> personList = new ArrayList<Person>();
    personList.add(new Person("鸣人", 8900, 18, "1", "螺旋丸"));
    personList.add(new Person("佐助", 8800, 18, "1", "须佐能乎"));
    personList.add(new Person("小樱", 7800, 17, "2", "治疗术"));
    personList.add(new Person("自来也", 8200, 30, "1", "通灵术"));
    personList.add(new Person("大蛇丸", 9500, 31, "1", "八岐大蛇"));
    personList.add(new Person("纲手", 7900, 29, "2", "百豪之术"));

    //必杀技list集合
    List<String> list = personList.stream().map(person -> person.getArea()).collect(Collectors.toList());
    System.out.println("必杀技集合toList:" + list);

    //大于18的年龄set集合
    Set<Integer> set = personList.stream().filter(person -> person.getAge() > 18).map(Person::getAge).collect(Collectors.toSet());
    System.out.println("大于18的年龄集合toSet:" + set);

    //战斗力大于8000的map集合 (写法1)
    Map<?, Person> map = personList.stream().filter(p -> p.getSalary() > 8000)
            .collect(Collectors.toMap(Person::getName, p -> p));
    System.out.println("战斗力大于8000的集合toMap:" + map);
    //战斗力大于8000的map集合 (写法2)
    Map<?, Person> map2 = personList.stream().filter(p -> p.getSalary() > 8000)
            .collect(Collectors.toMap(Person::getName, Function.identity()));
    System.out.println("战斗力大于8000的集合toMap:" + map2);
}

运行结果:

总结

好了,以上就是我总结的Stream的常见用法,感谢大家的阅读,如果有什么疑问或者建议,欢迎评论区留下你的独到见解~