java8 Stream接口的深入解读,stream接口内部的方法你都熟悉吗?

x33g5p2x  于2021-12-18 转载在 其他  
字(13.4k)|赞(0)|评价(0)|浏览(327)

Stream接口中每一个方法你都了解?

java8已经诞生很久了,stream-api 你是否已经非常的精通了?

了解 Stream-api 的前提需要熟悉常见函数式接口,对于每一个方法你是否有深入的解读过?

本篇文章主要目的是解读这个接口的每一个方法以及对应的应用场景。

跳过流的创建,重点讲解每一个方法的功能和应用场景。

stream用的最多的场景就是从数据库查询得到List数据,可能这些数据需要经过一些处理才可以暴露给前端。

其目的也是为了避免写复杂的SQL语句,很早以前的在没有持久层框架的开发,sql的基本功就要求非常高,后随着sql的持久层框架出现,很多场景更愿意用java代码来实现数据的转换,而Stream的产生也是为了更好的利用java来实现数据的转换。

持久层框架的出现,弱化了sql的功能,数据库层更偏向于数据存储,数据处理更适合用java等其它方式进行处理。

1、filter 过滤器(不符合的会被剔除)保留返回条件为true的元素
/** * @author 诗水人间 * @link 博客:{https://yumbo.blog.csdn.net/} * @link github:{https://github.com/1015770492} * @date 2021/9/19 21:32 */
public class FilterDemo {

    public static void main(String[] args) {
        // 保留符合条件的值,打印2,4
        Arrays.asList(1, 2, 3, 4, 5).stream().filter(x -> x % 2 == 0).forEach(System.out::println);
    }
}
2、map 映射、转换遍历元素,每一个遍历都需要return一个元素(可以是原来的也可以是新的)

这个map方法最主要的功能就是function类型函数式接口,就是将 A 转换成 B

/** * @author 诗水人间 * @link 博客:{https://yumbo.blog.csdn.net/} * @link github:{https://github.com/1015770492} * @date 2021/9/19 21:41 */
public class MapDemo {

    public static void main(String[] args) {
        // map映射,打印A1,A2,A3,A4,A5
        Arrays.asList(1,2,3,4,5).stream().map(x->"A"+x).forEach(System.out::println);
    }
}
3、mapToInt、mapToLong、mapToDouble 功能相似所以放一起,返回对应的int、long、double类型的stream

该功能和map类似,只不过是结果一定是对应类型的stream

/** * @author 诗水人间 * @link 博客:{https://yumbo.blog.csdn.net/} * @link github:{https://github.com/1015770492} * @date 2021/9/19 21:47 */
public class MapToNumDemo {

    public static void main(String[] args) {
        // 将字符串类型的数转换为int
        Arrays.asList("1","2","3").stream().mapToInt(Integer::parseInt).forEach(System.out::println);
        // 将字符串类型的数转换为long
        Arrays.asList("1","2","3").stream().mapToLong(Long::parseLong).forEach(System.out::println);
        // 将字符串类型的数转换为double
        Arrays.asList("1","2","3").stream().mapToDouble(Double::parseDouble).forEach(System.out::println);
        
    }
}
4、flatMap 处理二维或多维数组,将元素降维
public class FlatMapDemo {

    public static void main(String[] args) {
    	// 相当于二维数组
        final List<List<String>> lists = Arrays.asList(
                Arrays.asList("Hello World !!!", "I am Tom", "How are you doing?"),
                Arrays.asList("1", "2", "3"),
                Arrays.asList("你好世界!!!", "我是tom", "你正在做什么?")
        );
        String result="";
        // flatMap需要返回的上一个Stream类型的
        lists.stream().flatMap(x->x.stream()).forEach(System.out::println);
        lists.stream().map(x->x.stream()).forEach(System.out::println);
    }
}

5、flatMapToInt、flatMapToLong、flatMapToDouble 和前面的map类似,不懂怎么使用的可以看一下 flatMap的功能。

前面讲过了 faltMap 传入的函数式接口返回值类型一定是一个Stream流,故ToInt、ToLong、ToDouble就是返回IntStream、LongStream、DoubleStream

6、distinct 去重,和SQL的distinct意义相同,去除重复数据

底层是Set去重,意味着要使用distinct方法对象必须重写equals方法和hashCode方法(hashCode主要是为了避免没有必要的hash冲突)

/** * @author 诗水人间 * @link 博客:{https://yumbo.blog.csdn.net/} * @link github:{https://github.com/1015770492} * @date 2021/9/19 22:12 */
public class DistinctDemo {

    public static void main(String[] args) {
        // 打印 100,101,103,去除的底层原来就是调用对象的equals方法,
        // 实际上底层就是用一个Set保存所有元素,利用set自动去重,set的底层又是Hashmap,利用key的不重复
        Arrays.asList("100","101","100","103").stream().distinct().forEach(System.out::println);
    }
}
7、sorted 排序,元素必须实现Comparator接口

可以类比sql的 orderBy 需要注意的是对象一定要是可以比较的,也既是否实现了Comparator接口

/** * @author 诗水人间 * @link 博客:{https://yumbo.blog.csdn.net/} * @link github:{https://github.com/1015770492} * @date 2021/9/19 22:22 */
public class SortedDemo {

    public static void main(String[] args) {
        // 打印 13,15,20,23,50
        Arrays.asList(20, 15, 13, 23, 50).stream().sorted().forEach(System.out::println);
        // 如果自定义对象可以传入一个比较器
        Arrays.asList(21, 16, 14, 24, 51).stream().sorted(Integer::compareTo).forEach(System.out::println);
        // 不可以比较的可以传入一个Comparator接口
        Arrays.asList("22", "17", "15", "25", "52").stream().sorted((x, y) -> {
            return Integer.parseInt(x) - Integer.parseInt(y);
        }).forEach(System.out::println);
        // 不使用lambda表达式可以这样写,其它对象也可以参考下面的写法
        Arrays.asList("22", "17", "15", "25", "52").stream().sorted(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return Integer.parseInt(o1) - Integer.parseInt(o2);
            }
        }).forEach(System.out::println);

    }
}
8、peek 监视元素消费的触发器会在元素被Consumer消费前触发,不会修改元素本身,对象元素内部的属性可以被改变,但是对象本身不会被替换
import lombok.Builder;
import lombok.Data;
import java.util.Arrays;

/** * @author 诗水人间 * @link 博客:{https://yumbo.blog.csdn.net/} * @link github:{https://github.com/1015770492} * @date 2021/9/19 22:30 */
@Data
@Builder
class User {
    int age;
}

public class PeekDemo {

    public static void main(String[] args) {
        Arrays.asList(
                User.builder().age(1).build(),
                User.builder().age(2).build(),
                User.builder().age(3).build(),
                User.builder().age(4).build(),
                User.builder().age(5).build()
        ).stream().peek(x -> {
            System.out.println(x.age);
            x.setAge(x.age + 100);
            x = User.builder().age(3).build();
        }).forEach(System.out::println);
    }
}

peek不会修改stream的元素,诞生可以修改元素内部的属性(例如讲所有年龄加了100),打印结果

1
User(age=101)
2
User(age=102)
3
User(age=103)
4
User(age=104)
5
User(age=105)
9、limit 限流,用于限制返回的流元素个数
import java.util.stream.IntStream;

/** * @author 诗水人间 * @link 博客:{https://yumbo.blog.csdn.net/} * @link github:{https://github.com/1015770492} * @date 2021/9/19 22:54 */
public class LimitDemo {

    public static void main(String[] args) {
        // 打印1-10
        IntStream.rangeClosed(1,10).forEach(System.out::println);
        // 限制流元素个数为3个,打印1,2,3
        IntStream.rangeClosed(1,10).limit(3).forEach(System.out::println);

    }
}
10、skip 跳过元素,类似与sql 中的 limit 跳过元素的用法
import java.util.stream.IntStream;

/** * @author 诗水人间 * @link 博客:{https://yumbo.blog.csdn.net/} * @link github:{https://github.com/1015770492} * @date 2021/9/19 22:58 */
public class SkipDemo {

    public static void main(String[] args) {
        // 1-10,跳过前3个元素,限制元素为2个,故打印4,5
        IntStream.rangeClosed(1,10).skip(3).limit(2).forEach(System.out::println);
    }
}
11、forEach 遍历元素,遍历过后的元素就被消费了,返回值为void

这个方法在每一个示例代码中都用到了,也比较简单省略。

12、toArray Stream流元素转换为数组

方法比较简单省略说明
需要注意的是,它有2个方法,可以传一个Function类型的函数,意味着可以进行一些类型转换或者A->B的操作

13、reduce 处理元素得到最终的结果,有点像map、reduce的概念

reduce有3个方法,传一个参数,两个参数,三个参数

其中最常用的是传一个参数的

一、传一个接收两个参数的函数式接口的reduce
import java.util.Arrays;

/** * @author 诗水人间 * @link 博客:{https://yumbo.blog.csdn.net/} * @link github:{https://github.com/1015770492} * @date 2021/9/19 23:05 */
public class ReduceDemo {

    public static void main(String[] args) {

        System.out.println(Arrays.asList("1", "2", "3", "4").stream().reduce((x, y) -> {
            System.out.println(x + "---" + y);
            return "y"+y;
        }).get());
    }
}

会发现这里传入的接口需要接收两个参数,默认第一个参数x使用第一个元素,也就是"1",而另外两个reduce的区别就是这个这个默认值不一定是第一个元素,可以通过手动传入。
后将本次的操作的返回值作为下一个操作的x传入,然后接着从流中取出一个元素。
因此如下打印
第一次的返回值是 "y2",传入x,y=3,也就打印了y2---3

1---2
y2---3
y3---4
y4
二、2个参数的reduce,如上所属,就是这个初始的x可以手动传入
import java.util.Arrays;

/** * @author 诗水人间 * @link 博客:{https://yumbo.blog.csdn.net/} * @link github:{https://github.com/1015770492} * @date 2021/9/19 23:05 */
public class ReduceDemo {

    public static void main(String[] args) {

        System.out.println(Arrays.asList("1", "2", "3", "4").stream().reduce("init", (x, y) -> {
            System.out.println(x + "---" + y);
            return "y" + y;
        }));
    }
}

如下打印结果

init---1
y1---2
y2---3
y3---4
y4
三、3个参数的reduce 需要在parallel下才会触发第三个参数

第3个参数似乎是在并发的场景下使用的。其目的就是利用fork/join框架的思想,第二个参数只是一个拆分任务的方法,真正的合并结果的是第3个参数的lambda表达式,中间的那个lambda表达式作用是拆分大任务的过程

这个方法很有用哦,可以解决并发场景下的计算,而不用写一堆的ForkJoin接口对应的实现。

import java.util.Arrays;

/** * @author 诗水人间 * @link 博客:{https://yumbo.blog.csdn.net/} * @link github:{https://github.com/1015770492} * @date 2021/9/19 23:05 */
public class ReduceDemo {

    public static void main(String[] args) throws Exception {

        // 一定要是parallel并行流才会触发后面的
        final Integer reduce = Arrays.asList("1", "2", "3", "4").stream().parallel().reduce(0, (u, v) -> {
            System.out.println("第一个接口 ");
            System.out.println(u + "u---v" + v);
            return Integer.parseInt(v);// 如果改成return 1;那么打印的结果会是4,而不是10,相当于这里是做拆分任务用的
        }, (x, y) -> {
            System.out.println("第二个接口 ");
            System.out.println(x + "x---y" + y);
            return x+y;
        });
        System.out.println(reduce);
    }
}
14、collect 收集器,用于收集流中的元素,将其转换为集合List、Map、Collection等

此方法需要传入一个收集器,也就是Collector接口的实现,因此常用会用到工具类Collectors 其内部实现了该接口的一些方法,有常用的List、Map、Collection、Set。同时还可以实现数据的分组哦(按照类成员属性进行分组)

这个方法经常会使用到,因为常常我们会从数据库查询,经过一些操作后得到一些新的元素,然后我们会将这些新的元素收集起来做一些其它操作

收集元素为List
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/** * @author 诗水人间 * @link 博客:{https://yumbo.blog.csdn.net/} * @link github:{https://github.com/1015770492} * @date 2021/9/20 0:06 */
public class CollectDemo {

    public static void main(String[] args) {
        // 转换为字符串数组
        final List<String> collect = Arrays.asList(1, 2, 3, 4).stream()
        .map(String::valueOf).collect(Collectors.toList());
    }
}
List转Map
import lombok.Builder;
import lombok.Data;
import java.util.*;
import java.util.stream.Collectors;

/** * @author jinhua * @date 2021/8/19 21:30 */
@Data
@Builder
class User{
    String name;
    String sex;
    int age;
}

public class Demo {

    public static void main(String[] args) throws Exception {

        final ArrayList<User> users = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            users.add(User.builder().name(String.valueOf(i)).sex("男").age(11).build());
        }
        
        Map<String, Object> map = users.stream()
                .collect(Collectors.toMap(k -> k.name + k.sex, v -> v.age));

        map.forEach((k,v)->{
            System.out.println(k+" "+v);
        });
        
    }
}
分组

按照年龄分组

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
class User {
    int age;
}

/** * @author 诗水人间 * @link 博客:{https://yumbo.blog.csdn.net/} * @link github:{https://github.com/1015770492} * @date 2021/9/20 0:06 */
public class CollectDemo {

    public static void main(String[] args) {
        // 按照年龄进行分组,得到一个Map
        final Map<Integer, List<User>> collect = Arrays.asList(
                User.builder().age(1).build(),
                User.builder().age(2).build(),
                User.builder().age(1).build(),
                User.builder().age(2).build(),
                User.builder().age(5).build()
        ).stream().collect(Collectors.groupingBy(User::getAge));
        
    }
}
三个参数的collect 用于并发收集,也是使用forkjoin的思想进行收集

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

和前面一样用于提高收集的效率,进行并发收集
如下是测试案例分别在非parallel下 和 parallel下的情况

import java.util.ArrayList;
import java.util.Arrays;
import lombok.Builder;
import lombok.Data;

@Data
@Builder
class User {
    int age;
}
/** * @author 诗水人间 * @link 博客:{https://yumbo.blog.csdn.net/} * @link github:{https://github.com/1015770492} * @date 2021/9/20 0:06 */
public class CollectDemo {

    public static void main(String[] args) {

        ArrayList<Object> collect;
        collect = Arrays.asList(
                User.builder().age(1).build(),
                User.builder().age(2).build(),
                User.builder().age(1).build(),
                User.builder().age(2).build(),
                User.builder().age(5).build()
        ).stream().collect(ArrayList::new, ArrayList::add, (x, y) -> {
        });
        // 单线程进行收集
        System.out.println(collect);

        // 多线程下进行收集,会发现没有收集所有元素
        collect = Arrays.asList(
                User.builder().age(1).build(),
                User.builder().age(2).build(),
                User.builder().age(1).build(),
                User.builder().age(2).build(),
                User.builder().age(5).build()
        ).stream().parallel().collect(ArrayList::new, ArrayList::add, (x, y) -> {
        });
        System.out.println(collect);

        // 正确的并发收集
        collect = Arrays.asList(
                User.builder().age(1).build(),
                User.builder().age(2).build(),
                User.builder().age(1).build(),
                User.builder().age(2).build(),
                User.builder().age(5).build()
        ).stream().parallel().collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
        System.out.println(collect);

    }
}
[User(age=1), User(age=2), User(age=1), User(age=2), User(age=5)]
[User(age=1)]
[User(age=1), User(age=2), User(age=1), User(age=2), User(age=5)]
15、min、max、count 用法很简单

min和max都需要传入一个比较器,用于得到流中最大最小的元素。
count用于计算流中元素个数

16、anyMatch、allMatch、noneMatch 返回一个布尔值,类似与sql的exist
  1. anyMatch 存在某个符合条件
  2. allMatch 所有元素都符合条件
  3. noneMatch 没有一个元素符合条件
import java.util.Arrays;

/** * @author 诗水人间 * @link 博客:{https://yumbo.blog.csdn.net/} * @link github:{https://github.com/1015770492} * @date 2021/9/20 0:48 */
public class AnyMatch {

    public static void main(String[] args) {
        final boolean b = Arrays.asList(1, 2, 3, 4, 5).stream().anyMatch(x -> x > 6);
        final boolean b2 = Arrays.asList(1, 2, 3, 4, 5).stream().anyMatch(x -> x > 4);
        System.out.println(b);// false
        System.out.println(b2);// true
        final boolean b3 = Arrays.asList(1, 2, 3, 4, 5).stream().allMatch(x -> x > 0);
        final boolean b4 = Arrays.asList(1, 2, 3, 4, 5).stream().allMatch(x -> x > 5);
        System.out.println(b3);// true
        System.out.println(b4);// false
        final boolean b5 = Arrays.asList(1, 2, 3, 4, 5).stream().noneMatch(x -> x > 5);
        System.out.println(b5);// true
    }
}
17、findFirst 返回第一个匹配的元素

返回第一个元素

import java.util.Arrays;
import java.util.Optional;

/** * @author 诗水人间 * @link 博客:{https://yumbo.blog.csdn.net/} * @link github:{https://github.com/1015770492} * @date 2021/9/20 0:56 */
public class FindFirstDemo {

    public static void main(String[] args) {
        // 得到第一个偶数
        final Optional<Integer> first = Arrays.asList(1, 2, 3, 4, 5).stream()
        .filter(x -> x % 2 == 0)
        .findFirst();
        System.out.println(first.get());// 2
    }
}
18、findAny 返回任意一个元素,并行流下会区别于上面的 findFirst

并发情况下可能返回不同的结果

import java.util.Arrays;
import java.util.Optional;

/** * @author 诗水人间 * @link 博客:{https://yumbo.blog.csdn.net/} * @link github:{https://github.com/1015770492} * @date 2021/9/20 0:56 */
public class FindFirstDemo {

    public static void main(String[] args) {
        // 得到第一个偶数
        final Optional<Integer> first = Arrays.asList(1, 2, 3, 4, 5).stream()
                .parallel().findAny();
        System.out.println(first.get());// 数据量比较大的时候会随机返回一个,根据并行线程的最快的那个
    }
}
19、build 构建一个流

创建一个流

20、empty 得到一个空流,内部没有元素

静态方法

21、of

of方法是静态的方法,上面几个方法也是静态的,注意用于快速的得到一个流
of 有一个…表达式可以接收多个参数

import java.util.stream.Stream;

/** * @author 诗水人间 * @link 博客:{https://yumbo.blog.csdn.net/} * @link github:{https://github.com/1015770492} * @date 2021/9/20 0:56 */
public class FindFirstDemo {

    public static void main(String[] args) {
        final Stream<Integer> integerStream = Stream.of(1, 2, 3);// 快速的得到一个1,2,3流
    }
}
22、iterate 迭代器,用于生成一个迭代的流

iterate(final T seed, final UnaryOperator<T> f)

23、generate 生成器,生成一个无限流等操作

generate(Supplier<T> s)
传入一个Supply类型的函数式接口

24、concat 连接器,连接两个流

看到方法就知道怎么使用了
concat(Stream<? extends T> a, Stream<? extends T> b)

后续内容推荐 – Collectors

《深入解读Collectors集合收集器》

相关文章