Java-Stream学习笔记(上)

Java-Stream学习笔记(上)

  这篇博客仅谈论Stream的基础使用,不涉及Stream效率的问题以及并行流的讲解。等时机成熟,再更下一篇。

1.基础概念

1.1Stream的作用

  Stream能针对集合,数组等有限的数据,甚至是无限的数据进行操作。这种操作可以是多种多样的,比如过滤掉集合中不满足条件的数据,或是将集合转换为另外一种形式,而这种操作仅仅是一行的代码就能解决,简洁且优雅

比如:

从一系列Employee对象中过滤工资低于2000的员工,然后按照工资降序排列,最后打印输出。


常规代码:

  List<Employee> employeeList = Arrays.asList(
                new Employee("a", 20, 1000),
                new Employee("b", 30, 3000),
                new Employee("c", 35, 2500),
                new Employee("d", 22, 7000),
                new Employee("e", 37, 10000)
        );

        List<Employee> employeeListAfterFiltering =  new ArrayList<>();
        for (Employee employee: employeeList) {
   
            if(employee.getSalary()>=2000)
                employeeListAfterFiltering.add(employee);
        }

        employeeListAfterFiltering.sort((x,y) ->y.getSalary() -x.getSalary());

        employeeListAfterFiltering.forEach(System.out::println);

Stream代码:

Stream.of(
        new Employee("a", 20, 1000),
        new Employee("b", 30, 3000),
        new Employee("c", 35, 2500),
        new Employee("d", 22, 7000),
        new Employee("e", 37, 10000)
).filter(x->x.getSalary()>=2000).sorted((x, y)-> y.getSalary()-x.getSalary()).forEach(System.out::println);



1.2Stream的使用步骤

  Stream使用步骤分为三个步骤:

  1. 创建一个流。
  2. 指定一个流转换为另外一个流的中间操作,此操作可以有多个。
  3. 终止操作,此操作会执行强制之前的中间操作(中间操作是惰性操作,后面会解释)。从此以后,该流就不能够再使用。

在这里插入图片描述

需要注意两点:

1.中间操作都是将一个流转换为另一个流的操作。

2.终止操作一旦执行,最开始创建的流就不能再使用,否则会抛出IllegalStateException异常。验证如下:


测试代码:

@Test
    public void testBug(){
   
        List<Employee> employeeList = Arrays.asList(
                new Employee("a", 20, 1000),
                new Employee("b", 30, 3000),
                new Employee("c", 35, 2500),
                new Employee("d", 22, 7000),
                new Employee("e", 37, 10000)
        );

        Stream<Employee> stream = employeeList.stream();
        stream.filter(x->x.getAge()>20).forEach(System.out::println);

        //第二次使用流会报错
        stream.filter(x->x.getAge()>20).forEach(System.out::println);

    }

测试结果:

在这里插入图片描述


1.3Stream的特性

  1. Stream不会存储元素,这些元素要么是存储在底层的集合中,要么是按需生成的。比如无限流就是一个比较极端的例子,它不可能存储在Stream里面。

  2. 流的操作并不会修改数据源,即流的操作并不会影响到用于产生流的集合,数组等等。

  3. 流的操作是尽可能惰性执行,只要当终止操作执行时,才会强制执行中间操作。

    这里面我个人理解有两层意思:

    其一,体现惰性操作的优越性。充分分析中间操作后,再对中间操作进行批处理。比如针对无限流的一个例子,首先的操作是采用peek进行打印测试,然后再通过limit限制无限流个数,显然按照正常的想法来,程序必然在peek步骤死循环,但是,实际情况如下:

    测试代码:

      @Test
        public  void testInertiaOperation(){
         
            Stream.iterate(0,x->x+2).peek(System.out::print).limit(5).forEach(x->{
         });
        }
    

    结果:

    02468

    其二,**体现惰性操作的存在性。**因为中间操作都是将一个流转化为另一种流,根据这个特性设计如下代码:

    测试代码:

    @Test
    public  void testInertiaOperation1(){
         
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5).peek(System.out::println);
        System.out.println("我猜测我先于中间操作执行");
        stream.forEach(x->{
         });
    }
    

    结果:

    我猜测我先于中间操作执行
    1
    2
    3
    4
    5

    这表明惰性操作是的确存在的。



2.创建流

  根据数据源的不同以及涉及到的类的不同,分为如下三种创建类的方式。

  • 通过集合创建
  • 通过数组创建
  • 通过Stream创建

当然,创建流的方式不止上面三种,仅挑常用的来讲。

2.1通过集合创建流

  Collection及其子类都有一个实例方法stream(),利用该方法就能通过集合创建流。

测试代码:

Collection<Integer> collection =  new HashSet<>();
Stream<Integer> stream = collection.stream();



2.2通过数组创建流

  Arrays有一个静态方法stream(T[])可以把数组转为流。

测试代码:

Integer [] arr =  {
   1,2,3,4};
Stream<Integer> integerStream2 = Arrays.stream(arr);



2.3通过Stream创建流

  事实上,Stream可以通过可变参数列表创建流;创建仅一个元素的流;创建无限流;创建空流。

  1. 通过可变参数列表创建流Stream.of(T ...values) : Stream<T>

    测试代码:

     Stream<Integer> integerStream = Stream.of(1, 2, 3);
    
  2. 创建一个元素的流Stream.of(T t) : Stream<T>

    测试代码:

     Stream<Integer> integerStream = Stream.of(1);
    
  3. 通过静态方法创建无限流

    测试代码:

    /**
      *   提供一个Supplier产生无限流
      *   Stream.generate( Supplier<T> s)  : Stream<T>
      *   模拟创建一个 范围在0--100的无限流
      */
    Stream<Integer> integerStream1 = Stream.generate(() -> new Random().nextInt(101));
    
      /**   提供一个seed 即初始值,和一个UnaryOperator产生无限流,UnaryOperator继承自Function<T,T>
        *   Stream.iterate(final T seed, final UnaryOperator<T> f)  : Stream<T>
        *   可以将f理解为一个函数,初始化值(第一个值)就是seed ,第二个值就是f(seed),第三个值就是     *    f((seed)),如此类推。
        *    
        *     initial(即seed)   ,  f(x)
        *     1:                   initial
        *     2:                  f(initial
        *     3:                  f(f(initial))
        *     ......
        */
            Stream<Integer> integerStream3 = Stream.iterate(0, x -> x + 2);
    
  4. 通过静态方法创建空流

    测试代码:

    /**
     * Stream的静态方法创建空流
     */
    Stream<Object> empty = Stream.empty();
    



3.中间操作

  为了方便演示中间操作,提前讲一个终止操作 void forEach(Consumer<? super T> action),该终止操作会对流中的每个元素都执行一个action操作。比如打印输出。

  除此以外,为了方便演示,写了一个实体类(包含姓名,年龄,工资属性)

Employee:

package com.lordbao.entity;

import java.util.Objects;

/**
 * @Author Lord_Bao
 * @Date 2020/10/5 11:31
 * @Version 1.0
 */
public class Employee {
   
    private String username;
    private int age;
    private int salary;

    public Employee() {
   
    }

    public Employee(String username, int age, int salary) {
   
        this.username = username;
        this.age = age;
        this.salary = salary;
    }

    public String getUsername() {
   
        return username;
    }

    public void setUsername(String username) {
   
        this.username = username;
    }

    public int getAge() {
   
        return age;
    }

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

    public int getSalary() {
   
        return salary;
    }

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

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

    @Override
    public boolean equals(Object o) {
   
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return age == employee.age &&
                salary == employee.salary &&
                Objects.equals(username, employee.username);
    }

    @Override
    public int hashCode() {
   
        return Objects.hash(username, age, salary);
    }
}



3.1Filter过滤操作

  Stream<T> filter(Predicate<? super T> predicate)通过向filter方法传入一个predicate参数,以此过滤掉那些不满足条件的元素,并返回一个满足条件的元素的新流。

场景描述:

取出集合中工资大于3000的员工并打印输出


测试代码:

@Test
public void testFilter(){
   
    List<Employee> employeeList = Arrays.asList(
            new Employee("a", 20, 1000),
            new Employee("b", 30, 3000),
            new Employee("c", 35, 2500),
            new Employee("d", 22, 7000),
            new Employee("e", 37, 10000)
    );
    // 取出集合中工资大于3000的并打印输出
    employeeList.stream().filter(x->x.getSalary()>3000).forEach(System.out::println);
}

测试结果:

Employee{username=‘d’, age=22, salary=7000}
Employee{username=‘e’, age=37, salary=10000}



3.2Map映射操作

  <R> Stream<R> map(Function<? super T,? extends R> mapper)可以将Stream中的元素转换为其他的元素,并返回包含新元素的流。

场景描述:

取出集合中工资大于3000的员工的名字并打印输出


测试代码:

@Test
public void testMap(){
   
    List<Employee> employeeList = Arrays.asList(
            new Employee("a", 20, 1000),
            new Employee("b", 30, 3000),
            new Employee("c", 35, 2500),
            new Employee("d", 22, 7000),
            new Employee("e", 37, 10000)
    );

    // 取出集合中工资大于3000的名字并打印输出
    employeeList.stream().filter(x->x.getSalary()>3000).map(Employee::getUsername).forEach(System.out::println);
}

测试结果:

d
e



3.3FlatMap映射并拆解操作

  <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)。此方法是将每个元素映射成一个流,然后将这些流的中的元素都取出来合并,然后返回合并后的新流。

在这里插入图片描述

  按照这个逻辑将Stream中的每个元素都映射为Stream,然后将每个Stream都拆解,将拆解后的元素组装成

新的Stream。貌似看起来有点模糊,不如Map好理解啊。

  其实这里可以这么分析,什么元素能够映射为Stream呢?换个问题就是Stream是怎么创建的?大多数是通过集合,数组呗。那么我可不可以理解为本来的数据源就是集合或是数组,而它存储的元素恰好也是集合或是数组呢?

  按照这个思路,我设计了如下的例子。

测试代码:

 @Test
    public void testFlatMap2(){
   
        //1.二维数组
        Employee [] [] employeeArr1 ={
   
                {
    new Employee("a", 20, 1000),new Employee("c", 35, 2500)},
                {
    new Employee("b", 30, 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值