Java8新特性之流式操做

什么是流式操做

Java 8 API添加了一个新的抽象称为流Stream,可让你以一种声明的方式处理数据。java

Stream 使用一种相似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。程序员

Stream API能够极大提升Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。数据库

这种风格将要处理的元素集合看做一种流, 流在管道中传输, 而且能够在管道的节点上进行处理, 好比筛选, 排序,聚合等。数组

元素流在管道中通过中间操做(intermediate operation)的处理,最后由最终操做(terminal operation)获得前面处理的结果。数据结构

1.流式操做举例

1.1建立实体类

public class Person {

    private String name;
    private Integer age;
    private Integer score;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getScore() {
        return score;
    }

    public void setScore(Integer score) {
        this.score = score;
    }

    public Person() {
    }

    public Person(String name, Integer age, Integer score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
}
复制代码

1.2 传统的对象初始化方式

public class Program {
    public static void main(String[] args) {
        //使用构造器设置对象信息
// Person xiaomign = new Person("小明", 28, 90);

        //使用getter、setter方式设置对象信息
        Person xiaoming = new Person();
        xiaoming.setName("小明");
        xiaoming.setAge(18);
        xiaoming.setScore(90);
    }
}
复制代码

1.3 使用流式操做初始化对象

1.3.1 修改实体类

public class Person {

    private String name;
    private Integer age;
    private Integer score;

    public String getName() {
        return name;
    }

    public Person setName(String name) {
        this.name = name;
        return this;
    }

    public Integer getAge() {
        return age;
    }

    public Person setAge(Integer age) {
        this.age = age;
        return this;
    }

    public Integer getScore() {
        return score;
    }

    public Person setScore(Integer score) {
        this.score = score;
        return this;
    }

    public Person() {
    }

    public Person(String name, Integer age, Integer score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
}
复制代码

1.3.2 使用流式操做

//流式操做
xiaoming.setName("小明").setAge(20).setScore(100);
复制代码

2.集合的流式操做

集合的流式操做是Java8的一个新特性,流式操做不是一个数据结构,不负责任何的数据存储,它更像是一个迭代器,能够有序的获取数据源中的每个数据,而且能够对这些数据进行一些操做。流式操做的每个方法的返回值都是这个流的自己ide

2.1 流式操做的三个步骤

2.1.1 获取数据源:集合、数组

  • 设置数据源工具

    public class Data {
    
        /** * 数据源 */
        public static ArrayList<Person> getData() {
            ArrayList<Person> list = new ArrayList<Person>();
    
            list.add(new Person("小明", 18, 100));
            list.add(new Person("小丽", 19, 70));
            list.add(new Person("小王", 22, 85));
            list.add(new Person("小张", 20, 90));
            list.add(new Person("小黑", 21, 95));
            return list;
        }
    }
    复制代码
  • 获取数据源的方式性能

    public class Program {
        public static void main(String[] args) {
    
            // 获取数据源方式1
            Stream stream = Data.getData().stream();
    
            // 获取数据源方式2
            Stream.of(Data.getData());
            
            // 获取数据源方式3
            	//数据源为数组
        }
    }
    复制代码

2.1.2 对数据进行处理的过程:过滤、排序、映射等(中间操做)

中间操做1:filter

  • 使用filter自定义条件过滤数据优化

    // 中间操做1: filter
    // filter是一个过滤器,能够自定义一个过滤条件,将流中知足条件的元素保留
    // 查找集合中成绩小于80的学生
    List<Person> list = Data.getData().stream()
        .filter(ele -> ele.getScore() < 80)
        .collect(Collectors.toList());
    System.out.println(list);
    复制代码

中间操做2:distinct

  • 使用distinct实现去重操做this

    在数据源中添加剧复的数据

    list.add(new Person("小黑", 21, 95));	//此时list中有两个小黑
    复制代码

    在实体类中重写hashCode()和equals()方法

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(name, person.name) &&
            Objects.equals(age, person.age) &&
            Objects.equals(score, person.score);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name, age, score);
    }
    复制代码

    去重规则:

    • 先判断对象的hashCode()

    • 若是hashCode()相同再判断equals()

    // 中间操做2: distinct
    // distinct: 取出集合中不一样的元素
    // 去重规则:
    // 1.先判断对象的hashCode()
    // 2.若是hashCode()相同再判断equals()
    Data.getData().stream().distinct().forEach(System.out::println);
    复制代码

注意:若是小黑的数据相同却要保存两份,能够在hashCode()方法中返回一个随机数,随机数很小几率会相同,为了确保稳定性,能够将equals()方法改成返回false,这样能够保留两个信息相同的小黑。

中间操做3:sorted

  • 使用sorted()方法以成绩进行升序排序

    要求实体类实现Comparable接口并重写方法

    // 中间操做3: sorted
    // sorted: 对返回的元素进行排序
    // sorted(): 要求实体类实现Comparable接口并重写方法
    Data.getData().stream().sorted().forEach(System.out::println);
    复制代码

中间操做4:limit

  • 在数据源中取前三个数据

    // 中间操做4: limit
    // limit: 限制,只取流中前指定位的数据
    // 在数据源中取前三个数据
    Data.getData().stream().limit(3).forEach(System.out::println);
    复制代码

中间操做5:skip

  • 跳过前三个元素,取后面剩下的元素

    // 中间操做5: skip
    // skip: 跳过
    // 跳过前三个元素,取后面剩下的元素
    Data.getData().stream().skip(3).forEach(System.out::println);
    复制代码

中间操做6:map

元素映射,用指定的元素替换掉流中的元素

  • 使用map将对象替换为对象的名字

    // 中间操做6: map
    // map: 元素映射,用指定的元素替换掉流中的元素
    // 将流中的Person对象替换位他们的姓名
    Data.getData().stream().map(ele -> ele.getName()).forEach(System.out::println);
    复制代码

2.1.3 对流中数据的整合:转成集合、数量(最终操做)

最终操做1:collect

  • 转换为List

    public class Program {
        public static void main(String[] args) {
    
            // 获取数据源方式1
            Stream<Person> stream = Data.getData().stream();
    
            // 最终操做1: collect,配合Collectors使用
            // 将集合中的元素转换成List
            List<Person> list = stream.collect(Collectors.toList());
    
            System.out.println(list);
        }
    }
    复制代码

    运行结果

  • 转换为set

    // 将集合中的元素转换为Set
    Set<Person> set = stream.collect(Collectors.toSet());
    System.out.println(set);
    复制代码

    运行结果

  • 转换为map

    // 转换为Map(name为键,score为值)
            // 方式1
    // Map<String, Integer> map = stream.collect(Collectors.toMap(
    // ele -> ele.getName(),
    // ele -> ele.getScore()
    // )); 
            
            // 方式2 
            Map<String, Integer> map = stream.collect(Collectors.toMap(
                    Person::getName,
                    Person::getScore
            ));
    复制代码

    运行结果

最终操做2:reduce

reduce的思想

好比在计算一个数组中的元素的和时,首先会计算前两个数的和,而后拿着前两个数的和与第三个数求和,计算出结果后将三个数的和与第四个数相加,以此类推。

  • 计算数组中数据的和

    // 最终操做2: reduce(将数据汇总在一块儿)
    Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Optional<Integer> res = stream1.reduce((n1, n2) -> n1 + n2);
    // 获取到最终的返回值
    System.out.println(res.get());
    复制代码

  • 使用reduce计算Person对象中成绩的和

    // 计算Person中Score的和
    Optional<Person> res = stream.reduce(
        (n1, n2) -> new Person().setScore(n1.getScore() + n2.getScore())
    );
    System.out.println(res.get().getScore());
    复制代码

缺点:上面的写法每次都会产生一个临时的对象,产生了没必要要的性能损耗

  • 使用reduce计算Person对象中成绩的和(优化)

    // 计算Person中Score的和(使用临时变量,减小性能开销)
    Person temp = new Person();
    Optional<Person> res = stream.reduce(
        (n1, n2) -> temp.setScore(n1.getScore() + n2.getScore())
    );
    System.out.println(res.get().getScore());
    复制代码

最终操做3:max和min

  • 使用max找出Person中成绩最高的人

    // 最终操做3: max和min
    // 需求1: 找到集合中成绩最高的人的信息
    Person max = stream.max(
        (ele1, ele2) -> ele1.getScore() - ele2.getScore()
    ).get();
    System.out.println(max);
    复制代码

  • 使用min找出Person中成绩最低的人

    // 需求2: 找到集合中成绩最低的人的信息
    Person min = stream.min(
        (ele1, ele2) -> ele1.getScore() - ele2.getScore()
    ).get();
    System.out.println(min);
    复制代码

最终操做4:matching

  • 使用anyMatch查看集合中是否有成绩高于80的人

    // 判断集合中是否包含成绩大于80的学员
    boolean res1 = stream.anyMatch((ele) -> ele.getScore() > 80);
    System.out.println(res1);
    复制代码

  • 使用allMatch查看集合中的成绩是否所有高于60

    //查看集合中的人的成绩是否所有高于60
    boolean res2 = stream.allMatch((ele) -> ele.getScore() > 60);
    System.out.println(res2);
    复制代码

  • 使用noneMatch查看集合中的人的分数是否不包含80如下的

    boolean res3 = stream.noneMatch((ele) -> ele.getScore() < 80);
    System.out.println(res3);
    复制代码

最终操做5:count

  • 使用count计算元数据中有多少条数据

    // 最终操做5: 求元数据中有多少个元素
    long count = stream.count();
    System.out.println(count);
    复制代码

最终操做6:forEach

  • 使用forEach遍历集合中的元素

    // 最终操做6: forEach
    // stream.forEach(ele -> System.out.println(ele));
    stream.forEach(System.out::println);
    复制代码

最终操做7:findFirst和findAny

  • FindFirst: 获取流中的第一个元素 FindAny: 获取流中任意一个元素(并非随机获取元素) 对于串行流,结果等同于findFirst findAny用于并行流中可能会与findFirst同样,也可能不同
// FindFirst: 获取流中的第一个元素
// FindAny: 获取流中任意一个元素(并非随机获取元素)
// 对于串行流,结果等同于findFirst
// findAny用于并行流中可能会与findFirst同样,也可能不同
System.out.println(Data.getData().parallelStream().findFirst());
System.out.println(Data.getData().stream().findFirst());
System.out.println(Data.getData().parallelStream().findAny());
System.out.println(Data.getData().stream().findAny());
复制代码

最终操做的注意事项

  • 为何会被称为最终操做?

    Person max = stream.max(
        (ele1, ele2) -> ele1.getScore() - ele2.getScore()
    ).get();
    Person min = stream.min(
        (ele1, ele2) -> ele1.getScore() - ele2.getScore()
    ).get();
    复制代码

报错信息表示流正在被处理或者已经被关闭了,若是已经被关闭了再次调用固然会报错,这也是为何叫最终操做的缘由。

3.并行流

3.1 获取并行流的方式

// 并行流
// 获取并行流的两种方式
Data.getData().stream().parallel();
Data.getData().parallelStream();
复制代码

3.2 并行流与串行流对比

// 串行流: 19920ms
// 并行流: 12204ms
long startTime = System.currentTimeMillis();
//LongStream.rangeClosed(0L, 50000000000L)
// .reduce(Long::sum);
LongStream.rangeClosed(0L, 50000000000L)
    .parallel()
    .reduce(Long::sum);
long endTime = System.currentTimeMillis();

System.out.println(endTime - startTime);
复制代码

3.3 flatMap

String[] array = {"hello", "world"};
// 须要获取全部字符 List -> h, e, l, l, o, w, o, r, l, d
// Arrays.stream(array)
// .map(ele -> ele.split(""))
// .forEach(ele -> System.out.println(ele.length));
System.out.println(Arrays.stream(array)
                   .map(ele -> ele.split(""))
                   .flatMap(Arrays::stream)
                   .collect(Collectors.toList()));
复制代码

4.Collectors

Collectors是一个工具类,提供着若干个方法,返回一个Collector接口的实现类对象

4.1 maxBy

​ 经过指定的规则获取流中最大的元素

System.out.println(Data.getData().stream()
                .collect(Collectors.maxBy((ele1, ele2) -> ele1.getScore() - ele2.getScore())));
复制代码

4.2 minBy

​ 经过指定的规则获取流中最小的元素

System.out.println(Data.getData().stream()
                .collect(Collectors.minBy((ele1, ele2) -> ele1.getScore() - ele2.getScore())));
复制代码

4.3 joining

合并,将流中的元素,以字符串的形式拼接起来

// 把Person中的姓名拼成一个字符串
String res1 = Data.getData().stream()
    .map(Person::getName)
    .collect(Collectors.joining());
System.out.println(res1);
复制代码

String res2 = Data.getData().stream()
    .map(Person::getName)
    .collect(Collectors.joining("-"));
System.out.println(res2);
复制代码

String res3 = Data.getData().stream()
    .map(Person::getName)
    .collect(Collectors.joining("-", "{", "}"));
System.out.println(res3);
复制代码

4.4 summingInt

计算int类型的和,将流中的元素映射为int类型的元素进行求和

  • 将Person对象的成绩进行求和

    // 将Person对象的成绩进行求和
    System.out.println(Data.getData().stream()
                       .collect(Collectors.summingInt(ele -> ele.getScore())));
    复制代码

4.5 averagingInt

计算int类型的平均值

  • 计算不及格学生的平均成绩

    System.out.println(Data.getData().stream()
                       .filter(ele -> ele.getScore() < 60)
                       .collect(Collectors.averagingInt(Person::getScore)));
    复制代码

4.6 summarizingInt

将流中的元素映射成int类型的元素,获取这些数据的描述信息

System.out.println(Data.getData().stream()
                   .collect(Collectors.summarizingInt(ele -> ele.getScore())));
复制代码

相关文章
相关标签/搜索