JDK1.8新特性之Stream

1 JDK1.8中新增的Stream用法

  1. Stream有什么用?有哪些使用场景?

1.1 什么是Stream

流是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

注意

  1. Stream自己不会存储元素
  2. Stream不会改变原对象。相反,他们会返回一个持有结果的新Stream。
  3. Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

1.1.1 Stream操作的三个步骤

  • 创建Stream
    一个数据源(如:集合、数组),获取一个流
  • 中间操作
    一个中间操作链,对数据源的数据进行处理
  • 终止操作(终端操作)
    一个终止操作,执行中间操作链,并产生结果。
1.1.1.1 创建Stream的几种方式
  1. 可以通过Collection系列集合提供的stream()或parallelStream()
public static void main(String[] args) {
  List<String> list = new ArrayList<>();
     Stream<String> stream1 = list.stream();
 }
  1. 通过Arrays中的静态方法stream()获取数组流
public static void main(String[] args) {
 Apple[] apples = new Apple[10];
    Stream<Apple> stream = Arrays.stream(apples);
}
  1. 通过Stream类中的静态方法of()
Stream<String> stream = Stream.of("aa","bb","cc");
  1. 创建无限流(有两种方式:迭代和生成)
//迭代 
Stream<Integer> stream = Stream.iterate(0,(x) -> x + 2);
stream.limit(10).forEach(System.out::println);

//生成
Stream.generate(() -> Math.random()).limit(5).forEach(System.out::println);
1.1.1.2 Stream的中间操作
1.1.1.2.1 筛选与切片

filter ----- 接收 Lambda,从流中排除某些元素
limit ----- 截断流,使其元素不超过给定数量
skip(n) ----- 跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补
distinct ------ 筛选,通过流所生成元素的hashCode()和equals()去除重复元素

注意:多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”

1.1.1.2.1.1 filter的使用

filter方法当中的参数是Predicate函数式接口,当中是boolean test(T t);适合用来判断过滤
下面使用例子:筛选年龄大于35岁的员工

public class Main {

    static List<Employee> employees = Arrays.asList(
            new Employee("张三",18),
            new Employee("李四",58),
            new Employee("王五",26),
            new Employee("赵六",36),
            new Employee("田七",12)
    );

    public static void main(String[] args) {
        //中间操作
        Stream<Employee> stream = employees.stream()
                .filter((e) -> {
                    System.out.println("Stream API的中间操作");
            return e.getAge() > 35;
        });
        //终止操作:一次性执行全部内容,即“惰性求值”
        stream.forEach(System.out::println);
    }

}

运行结果如下:

Stream API的中间操作
Stream API的中间操作
Employee{name='李四', age=58}
Stream API的中间操作
Stream API的中间操作
Employee{name='赵六', age=36}
Stream API的中间操作
1.1.1.2.1.2 limit的使用

依然是上面的代码加上limit的使用代码如下:

public class Main {
    static List<Employee> employees = Arrays.asList(
            new Employee("张三",18),
            new Employee("李四",58),
            new Employee("王五",26),
            new Employee("赵六",36),
            new Employee("田七",12)
    );
    public static void main(String[] args) {
        //中间操作
        employees.stream()
                .filter((e) -> {
                    System.out.println("Stream API的中间操作");
                    return e.getAge() > 35;
        }).limit(1).forEach(System.out::println);
    }
}

执行结果如下

Stream API的中间操作
Stream API的中间操作
Employee{name='李四', age=58}

我们从结果可以看出加上limit的使用后stream在遍历后当满足limit中的指定的数量后便停止了后面的遍历操作。因此在某种情况是可以提供一定的性能的。

1.1.1.2.1.3 skip的使用

skip(n) ----- 跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补。

使用例子:

public class Main {
    static List<Employee> employees = Arrays.asList(
            new Employee("张三",18),
            new Employee("李四",58),
            new Employee("王五",26),
            new Employee("赵六",36),
            new Employee("田七",41)
    );
    public static void main(String[] args) {
        //中间操作
        employees.stream()
                .filter((e) -> {
                    System.out.println("Stream API的中间操作");
                    return e.getAge() > 35;
        }).skip(1).forEach(System.out::println);
    }
}

执行结果如下:

Stream API的中间操作
Stream API的中间操作
Stream API的中间操作
Stream API的中间操作
Employee{name='赵六', age=36}
Stream API的中间操作
Employee{name='田七', age=41}

这里skip(n)就表示先去除满足filter条件的前n条。

1.1.1.2.1.4 distinct的使用

distinct的作用是去重,但需要将元素的hashCode()和equals()重写。否则起不到去重作用。

public class Main {
    static List<Employee> employees = Arrays.asList(
            new Employee("张三",18),
            new Employee("李四",58),
            new Employee("王五",26),
            new Employee("赵六",36),
            new Employee("田七",41),
            new Employee("田七",41),
            new Employee("田七",41)
    );
    public static void main(String[] args) {
        //中间操作
        employees.stream()
                .filter((e) -> {
                    System.out.println("Stream API的中间操作");
                    return e.getAge() > 35;
        }).skip(1).distinct().forEach(System.out::println);
    }
}

运行结果

Stream API的中间操作
Stream API的中间操作
Stream API的中间操作
Stream API的中间操作
Employee{name='赵六', age=36}
Stream API的中间操作
Employee{name='田七', age=41}
Stream API的中间操作
Stream API的中间操作
1.1.1.2.2 映射

映射:map ----- 接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
   flatMap ----- 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

map方法中的参数是Function函数式接口类型,说明他是实现功能作用的。
下面是使用map方法实现链表中的字符串转大写

public static void main(String[] args) {
        List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","eee");
        list.stream().map((str) -> str.toUpperCase()).forEach(System.out::println);
    }

实现结果:

AAA
BBB
CCC
DDD
EEE
1.1.1.2.3 排序

排序:sorted() ---- 自然排序(Comparable)
   sorted(Comparator com) ----- 定制排序(Comparator)

下面是根据Employee对象的名字排序,如果名字一样按年龄排序。

public class Main {
    static List<Employee> employees = Arrays.asList(
            new Employee("张三","18"),
            new Employee("李四","58"),
            new Employee("王五","26"),
            new Employee("赵六","36"),
            new Employee("田七","41")
    );
    public static void main(String[] args) {
        List<String> list = Arrays.asList("ccc","aaa","bbb","ddd","eee");
        list.stream().sorted().forEach(System.out::println);
        System.out.println("------------------------------------");
        employees.stream().sorted((e1,e2) -> {
            if(e1.getName().equals(e2.getName())){
                return e1.getAge().compareTo(e2.getAge());
            }else{
                return e1.getName().compareTo(e2.getName());
            }
        }).forEach(System.out::println);
    }
}
1.1.1.3 Stream的终止操作
1.1.1.3.1 查找与匹配

allMatch ----- 检查是否匹配所有元素
anyMatch ---- 检查是否至少匹配一个元素
noneMatch ----- 检查是否没有匹配所有元素
findFirst ------ 返回第一个元素
findAny ------ 返回当前流中的任意元素
count ------ 返回流中元素的总个数
max ------ 返回流中最大值
min ------ 返回流中最小值

使用操作如下代码:

public class Main {
    static List<Employee> employees = Arrays.asList(
            new Employee("张三",18,9999, Employee.Status.FREE),
            new Employee("李四",58,5555,Employee.Status.BUSY),
            new Employee("王五",26,3333,Employee.Status.VOCATION),
            new Employee("赵六",36,6666, Employee.Status.FREE),
            new Employee("田七",41,8888, Employee.Status.BUSY)
    );
    public static void main(String[] args) {
        //查询是否所有员工都在BUSY状态
        boolean b1 = employees.stream().allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
        System.out.println(b1);

        //查询是否存在至少有一个员工在BUSY状态
        boolean b2 = employees.stream().anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
        System.out.println(b2);

        //查询是否没有员工在BUSY状态
        boolean b3 = employees.stream().noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
        System.out.println(b3);

        //查询员工工资最低的一个
        //这个Optional主要是为了防止返回为空所用的容器对象
        Optional<Employee> op = employees.stream().sorted((e1, e2) -> Double.compare(e1.getSalary(),e2.getSalary())).findFirst();
        System.out.println(op.get());

        //查询状态为FREE的任意一个员工,这里采用parallelStream是并行流是多线程状态下操作流
        Optional<Employee> op2 = employees.parallelStream().filter((e) -> e.getStatus().equals(Employee.Status.FREE)).findAny();
        System.out.println(op2.get());

        //返回流中元素个数
        Long count = employees.stream().count();
        System.out.println(count);

        //查询工资最高的员工
        Optional<Employee> op1 = employees.stream().max((e1,e2) -> Double.compare(e1.getSalary(),e2.getSalary()));
        System.out.println(op1.get());

        //查询最低工资是多少
        Optional<Double> op5 = employees.stream().map(Employee::getSalary).min(Double::compare);
        System.out.println(op5.get());
    }
}

下面是Employee类

public class Employee {

    private String name;
    private Integer age;
    private double salary;
    private Status status;

    public Employee(String name, Integer age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public Employee(String name, Integer age, double salary, Status status) {
        this.name = name;
        this.age = age;
        this.salary = salary;
        this.status = status;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public Status getStatus() {
        return status;
    }

    public void setStatus(Status status) {
        this.status = status;
    }

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

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                ", status=" + status +
                '}';
    }

    public enum Status{
        FREE,
        BUSY,
        VOCATION;
    }
}
1.1.1.3.2 归约

reduce(T identity, BinaryOperator) / reduce(BinaryOperator) ----可以将流中元素反复结合起来,得到一个值。

需求:对一个Integer类型的数组求和

public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);

        Integer sum = list.stream().reduce(0,(x,y) -> x + y);
        System.out.println(sum);
    }

reduce方法的第一个参数表示从0开始,而第二个参数是BinaryOperator<T> accumulator类型的函数式接口,它的执行过程:先把第一个参数作为起始值,x先作为起始值0进行程序执行,然后从流中取出元素作为y(这里首先取第一个1)进行计算,再将x+y的结果作为x依次进行执行。

再举个使用例子:计算员工工资总和。
思路:我们应该先使用map方法将流中用户的薪水取出再进行归约计算

public class Main {

    static List<Employee> employees = Arrays.asList(
            new Employee("张三",18,9999, Employee.Status.FREE),
            new Employee("李四",58,5555,Employee.Status.BUSY),
            new Employee("王五",26,3333,Employee.Status.VOCATION),
            new Employee("赵六",36,6666, Employee.Status.FREE),
            new Employee("田七",41,8888, Employee.Status.BUSY)
    );

    public static void main(String[] args) {
        Optional<Double> op = employees.stream().map(Employee::getSalary).reduce(Double::sum);
        System.out.println(op.get());
    }
}

这种map和reduce的连接通常称为map-reduce模式,因Google用它来进行网络搜索而出名。

1.1.1.3.3 收集

collect ----- 将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法,该方法的实现决定了如何对流执行收集操作(如收集到List、Set、Map)。但是Collectors实现类提供了很多静态方法,可以方便地创建常见收集器实例。

实例:收集员工的名字

public class Main {

    static List<Employee> employees = Arrays.asList(
            new Employee("张三",18,9999, Employee.Status.FREE),
            new Employee("李四",58,5555,Employee.Status.BUSY),
            new Employee("王五",26,3333,Employee.Status.VOCATION),
            new Employee("赵六",36,6666, Employee.Status.FREE),
            new Employee("田七",41,8888, Employee.Status.BUSY)
    );

    public static void main(String[] args) {
        List<String> list = employees.stream().map(Employee::getName).collect(Collectors.toList());
        list.forEach(System.out::println);
    }
}

还可以指定收集到不同集合类型中
下面是收集到HashSet中

public class Main {

    static List<Employee> employees = Arrays.asList(
            new Employee("张三",18,9999, Employee.Status.FREE),
            new Employee("李四",58,5555,Employee.Status.BUSY),
            new Employee("王五",26,3333,Employee.Status.VOCATION),
            new Employee("赵六",36,6666, Employee.Status.FREE),
            new Employee("田七",41,8888, Employee.Status.BUSY)
    );

    public static void main(String[] args) {
        HashSet<String> hs = employees.stream().map(Employee::getName).collect(Collectors.toCollection(HashSet::new));
        hs.forEach(System.out::println);
    }
}

1.2 并行流与顺序流

1.2.1 什么是并行流

并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。

Java8中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API可以声明性地通过parallel()与sequential()在并行流与顺序流之间进行切换。在使用并行流之前先要对Fork/Join框架有个认识和理解。

1.2.1.1 了解Fork/Join框架

在这里插入图片描述
那么Fork/Join框架和传统多线程池的区别在哪呢?

1.2.1.2 Fork/Join框架和传统多线程池的区别

在这里插入图片描述
简单来说就是传统的线程池的“CPU利用率不高”,当某个线程工作完成后就“闲”着等其他线程完成工作了(阿姨你快点嘛阿姨,别磨磨蹭蹭的。。),而在Fork/Join中某一线程完成后会对其他还未完成工作的线程手中未完成的工作“偷”一些来帮助整体工作的进行。这一机制就叫“工作窃取”模式。

那么ForkJoin为什么在1.8之前人们使用不多呢?因为使用起来麻烦。下面手写使用ForkJoin进行累加操作。体验ForkJoin的效率。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值