JDK1.8 & Stream API的使用

455 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第9天,点击查看活动详情

什么是 Stream?

Stream(流)是一个来自数据源的元素队列并支持聚合操作

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
  • 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

和以前的Collection操作不同, Stream操作还有两个基础的特征:

  • Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
  • 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

下面我们使用一下常用的 API以及一些配合使用

首先我们准备一个 实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserTestDo {
    private String name;

    private String email;

    private Integer age;

}

forEach

void forEach(Consumer<? super T> action);

这里我们可以看到他传入的参数是一个 函数接口 Consumer 有入参没返回值的

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);
   
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
 public static void main(String[] args) {
        List<UserTestDo> list =new ArrayList<>();
        UserTestDo  userTestDo = new UserTestDo("小明","3972088@qq.com",10);
        UserTestDo  userTestDo1 = new UserTestDo("大肖","3922088@qq.com",10);
        UserTestDo  userTestDo2 = new UserTestDo("小明","3972088@qq.com",10);
        list.add(userTestDo);
        list.add(userTestDo1);
        list.add(userTestDo2);
        //这是一种
        list.stream().forEach((msg)->{
            System.out.println("msg = " + msg);
        });
        //第二种简写 方法引用
        list.stream().forEach(System.out::println);
    }
}

输出结果:

msg = UserTestDo(name=小明, email=3972088@qq.com, age=10)
msg = UserTestDo(name=大肖, email=3922088@qq.com, age=10)
msg = UserTestDo(name=小明, email=3972088@qq.com, age=10)
UserTestDo(name=小明, email=3972088@qq.com, age=10)
UserTestDo(name=大肖, email=3922088@qq.com, age=10)
UserTestDo(name=小明, email=3972088@qq.com, age=10)

map

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

这里我们可以看到他入参为 Function 函数式接口 有入参也有返回值

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}
public static void main(String[] args) {
    List<UserTestDo> list =new ArrayList<>();
    UserTestDo  userTestDo = new UserTestDo("小明","3972088@qq.com",10);
    UserTestDo  userTestDo1 = new UserTestDo("大肖","3922088@qq.com",10);
    UserTestDo  userTestDo2 = new UserTestDo("小明","3972088@qq.com",10);
    list.add(userTestDo);
    list.add(userTestDo1);
    list.add(userTestDo2);
    //这是一种
    list.stream().map(e->{
       if(e.getName().equals("大肖")){
           e.setAge(20);
       }
        return e;
    }).forEach(System.out::println);


}

输出结果:

UserTestDo(name=小明, email=3972088@qq.com, age=10)
UserTestDo(name=大肖, email=3922088@qq.com, age=20)
UserTestDo(name=小明, email=3972088@qq.com, age=10)

map 可以理解为 把每个对像可以做一个特殊处理处理并收集成新的对象返回

filter

Stream<T> filter(Predicate<? super T> predicate);

这里的入参是 Predicate 函数式接口 有入参切返回 boolean类型

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

使用:

public static void main(String[] args) {
    List<UserTestDo> list =new ArrayList<>();
    UserTestDo  userTestDo = new UserTestDo("小明","3972088@qq.com",10);
    UserTestDo  userTestDo1 = new UserTestDo("大肖","3922088@qq.com",10);
    UserTestDo  userTestDo2 = new UserTestDo("小明","3972088@qq.com",10);
    list.add(userTestDo);
    list.add(userTestDo1);
    list.add(userTestDo2);
    //这是一种
    list.stream().filter(e -> {
        if(e.getName().startsWith("大")){
            //过滤掉不会返回
            return false;
        }
        return true;
    }).forEach(System.out::println);

结果输出:

UserTestDo(name=小明, email=3972088@qq.com, age=10)
UserTestDo(name=小明, email=3972088@qq.com, age=10)

filter 可以理解为进行数据过滤返回符合条件的数据

limit

Stream<T> limit(long maxSize);

使用

public static void main(String[] args) {
    List<UserTestDo> list =new ArrayList<>();
    UserTestDo  userTestDo = new UserTestDo("小明","3972088@qq.com",10);
    UserTestDo  userTestDo1 = new UserTestDo("大肖","3922088@qq.com",10);
    UserTestDo  userTestDo2 = new UserTestDo("小明","3972088@qq.com",10);
    list.add(userTestDo);
    list.add(userTestDo1);
    list.add(userTestDo2);
    
    list.stream().limit(2).forEach(System.out::println);

}
UserTestDo(name=小明, email=3972088@qq.com, age=10)
UserTestDo(name=大肖, email=3922088@qq.com, age=10)

limit 可以理解为获取集合的前几个传入是几就是前几个

sorted

Stream<T> sorted(Comparator<? super T> comparator);//可设置规则
Stream<T> sorted();//默认的自然数升序

使用:

public static void main(String[] args) {
    List<UserTestDo> list =new ArrayList<>();
    UserTestDo  userTestDo = new UserTestDo("小明","3972088@qq.com",13);
    UserTestDo  userTestDo1 = new UserTestDo("大肖","3922088@qq.com",77);
    UserTestDo  userTestDo2 = new UserTestDo("小明","3972088@qq.com",12);
    list.add(userTestDo);
    list.add(userTestDo1);
    list.add(userTestDo2);
    Stream.of(13,2,11,6,99).sorted().forEach(e->{
        System.out.println("不指定规则进行默认升序排序 = " + e);
    });
    //倒叙
    list.stream().sorted((m1,m2)->m2.getAge()-m1.getAge()).forEach(e->{
        System.out.println("倒叙使用的是Comparator.compare方法 = " + e);
    });

    //升序
    list.stream().sorted(Comparator.comparing(UserTestDo::getAge)).forEach(e->{
        System.out.println("升序使用的是Comparator.comparing 静态方法 = " + e);
    });
    //倒叙
    list.stream().sorted(Comparator.comparing(UserTestDo::getAge).reversed()).forEach(e->{
        System.out.println("降序使用的是Comparator.comparing 静态方法 = " + e);
    });
}

输出结果:

不指定规则进行默认升序排序 = 2
不指定规则进行默认升序排序 = 6
不指定规则进行默认升序排序 = 11
不指定规则进行默认升序排序 = 13
不指定规则进行默认升序排序 = 99
倒叙使用的是Comparator.compare方法 = UserTestDo(name=大肖, email=3922088@qq.com, age=77)
倒叙使用的是Comparator.compare方法 = UserTestDo(name=小明, email=3972088@qq.com, age=13)
倒叙使用的是Comparator.compare方法 = UserTestDo(name=小明, email=3972088@qq.com, age=12)
升序使用的是Comparator.comparing 静态方法 = UserTestDo(name=小明, email=3972088@qq.com, age=12)
升序使用的是Comparator.comparing 静态方法 = UserTestDo(name=小明, email=3972088@qq.com, age=13)
升序使用的是Comparator.comparing 静态方法 = UserTestDo(name=大肖, email=3922088@qq.com, age=77)
降序使用的是Comparator.comparing 静态方法 = UserTestDo(name=大肖, email=3922088@qq.com, age=77)
降序使用的是Comparator.comparing 静态方法 = UserTestDo(name=小明, email=3972088@qq.com, age=13)
降序使用的是Comparator.comparing 静态方法 = UserTestDo(name=小明, email=3972088@qq.com, age=12)

distinct

Stream<T> distinct();

使用:

public static void main(String[] args) {
    List<UserTestDo> list =new ArrayList<>();
    UserTestDo  userTestDo = new UserTestDo("小明","3972088@qq.com",13);
    UserTestDo  userTestDo1 = new UserTestDo("大肖","3922088@qq.com",77);
    UserTestDo  userTestDo2 = new UserTestDo("小明","3972088@qq.com",13);
    list.add(userTestDo);
    list.add(userTestDo1);
    list.add(userTestDo2);
    list.stream().distinct().forEach(System.out::println);
}

输出结果:

UserTestDo(name=小明, email=3972088@qq.com, age=13)
UserTestDo(name=大肖, email=3922088@qq.com, age=77)

这里可以看到去重成功了。这里有个小细节需要要注意下 lombok是重写了的 equals的方法的
如果我们的实体类不用他的 注解 且不去重写 equals方法 是会去重失败的

public class UserTestDo {
    private String name;

    private String email;

    private Integer age;

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

    public void setEmail(String email) {
        this.email = email;
    }

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

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }

    public Integer getAge() {
        return age;
    }

    public UserTestDo() {
    }

    public UserTestDo(String name, String email, Integer age) {
        this.name = name;
        this.email = email;
        this.age = age;
    }

    @Override
    public String toString() {
        return "UserTestDo{" +
                "name='" + name + ''' +
                ", email='" + email + ''' +
                ", age=" + age +
                '}';
    }

}

使用:

public static void main(String[] args) {
    List<UserTestDo> list =new ArrayList<>();
    UserTestDo  userTestDo = new UserTestDo("小明","3972088@qq.com",13);
    UserTestDo  userTestDo1 = new UserTestDo("大肖","3922088@qq.com",77);
    UserTestDo  userTestDo2 = new UserTestDo("小明","3972088@qq.com",13);
    list.add(userTestDo);
    list.add(userTestDo1);
    list.add(userTestDo2);
    list.stream().distinct().forEach(System.out::println);
}

发现结果并没有去重掉原因就是 我们实体类中没有重写 equals 方法

UserTestDo{name='小明', email='3972088@qq.com', age=13}
UserTestDo{name='大肖', email='3922088@qq.com', age=77}
UserTestDo{name='小明', email='3972088@qq.com', age=13}

下面我们将 equals进行重写

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    UserTestDo that = (UserTestDo) o;
    return Objects.equals(name, that.name) && Objects.equals(email, that.email) && Objects.equals(age, that.age);
}

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

再去执行就会发现成功去重了

UserTestDo{name='小明', email='3972088@qq.com', age=13}
UserTestDo{name='大肖', email='3922088@qq.com', age=77}

collect

<R, A> R collect(Collector<? super T, A, R> collector);
<R> R collect(Supplier<R> supplier,
              BiConsumer<R, ? super T> accumulator,
              BiConsumer<R, R> combiner);

我们从collect 点进来就能看到 Cllectors他里面有很多操作 list转换map 分组等方法 image.png 下面我们演示一个 收集id的操作

public static void main(String[] args) {
    List<UserTestDo> list =new ArrayList<>();
    UserTestDo  userTestDo = new UserTestDo(1,"小明","3972088@qq.com",13);
    UserTestDo  userTestDo1 = new UserTestDo(2,"大肖","3922088@qq.com",77);
    UserTestDo  userTestDo2 = new UserTestDo(3,"小明","3972088@qq.com",13);
    list.add(userTestDo);
    list.add(userTestDo1);
    list.add(userTestDo2);
    String ids = list.stream().map(e -> e.getId().toString()).collect(Collectors.joining(","));
    System.out.println("ids = " + ids);
}

输出结果:

ids = 1,2,3

转换为map 可以看到toMap的 方法入参都是函数式接口

public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                Function<? super T, ? extends U> valueMapper) {
    return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}

使用:

public static void main(String[] args) {
    List<UserTestDo> list =new ArrayList<>();
    UserTestDo  userTestDo = new UserTestDo(1,"小明","3972088@qq.com",13);
    UserTestDo  userTestDo1 = new UserTestDo(2,"大肖","3922088@qq.com",77);
    UserTestDo  userTestDo2 = new UserTestDo(3,"小明","3972088@qq.com",13);
    list.add(userTestDo);
    list.add(userTestDo1);
    list.add(userTestDo2);
    Map<Integer, UserTestDo> collect = list.stream().collect(Collectors.toMap(UserTestDo::getId, UserTestDo -> UserTestDo));
    collect.forEach((key,val)->{
        System.out.println("key = " + key);
        System.out.println("val = " + val);
    });
}

输出结果:

key = 1
val = UserTestDo(id=1, name=小明, email=3972088@qq.com, age=13)
key = 2
val = UserTestDo(id=2, name=大肖, email=3922088@qq.com, age=77)
key = 3
val = UserTestDo(id=3, name=小明, email=3972088@qq.com, age=13)

进行分组 并进行计数

public static void main(String[] args) {
    List<UserTestDo> list =new ArrayList<>();
    UserTestDo  userTestDo = new UserTestDo(1,"小明","3972088@qq.com",13);
    UserTestDo  userTestDo1 = new UserTestDo(2,"大肖","3922088@qq.com",77);
    UserTestDo  userTestDo2 = new UserTestDo(3,"小明","3972088@qq.com",13);
    list.add(userTestDo);
    list.add(userTestDo1);
    list.add(userTestDo2);
    Map<String, Long> collect = list.stream().collect(Collectors.groupingBy(UserTestDo::getName, Collectors.counting()));
    collect.forEach((key,val)->{
        System.out.println("元素 = " + key);
        System.out.println("出现次数 = " + val);
    });
}

输出结果:

元素 = 大肖
出现次数 = 1
元素 = 小明
出现次数 = 2

Stream API好玩的方法还有很多,感兴趣的可以多去试试。明天见啦🥰🥰🥰