Java8新特性详解(二)Stream API使用

前言

此文章中用到了Lambda表达式、方法引用、构造器引用、四大内置核心函数式接口 等新特性,如果不了解的先看前一篇文章 https://blog.csdn.net/weixin_43833851/article/details/129507783

Stream API 有什么作用?

可以像SQL语句一样对集合、数组进行操作。

特点:

① Stream不会存储元素

② Stream 不会改变原对象

③ Stream 的 中间操作方法 要等到 终止操作方法 被调用时才会执行

一,Stream的创建方式

public class TestStream {
    public static void main(String[] args) {
        //1,通过集合的stream方法
        Stream<String> stream1 = new ArrayList<String>().stream();
        Stream<String> stream2 = new HashSet<String>().stream();

        //2.通过Arrays的stream方法
        Stream<Integer> stream3 = Arrays.stream(new Integer[]{1, 2, 3});

        //3.通过Stream类本身提供的方法
        //3.1 of方法
        Stream<String> stream4 = Stream.of("aaa", "bbb");

        //3.2 iterate方法,会像死循环一样不停的运行,所以用了limit(10)限制其只迭代10次
        //第一个参数是初始值,第二个参数是Function接口的子接口
        Stream<Integer> stream5 = Stream.iterate(0, x -> x + 2);
        stream5.limit(10).forEach(System.out::println);
        //等同于下面的代码
        int i=0;
        int x = 0;
        while (true){
            if(i>=10){
                break;
            }
            x = x+2;
            System.out.println(x);
            i++;
        }


        //3.3 generate方法,参数是一个Supplier接口,会像死循环一样不停的运行,所以用了limit(10)限制其只迭代10次
        Stream<Double> stream6 = Stream.generate(()->Math.random());
        stream6.limit(10).forEach(System.out::println);
    }
}

二,中间操作

多个中间操作方法可以连起来使用,在调用终止操作方法前,中间操作不会执行,而在终止操作时一次性执行完成,这叫"惰性求值"

中间操作的方法包含:

方法

说明

filter(Predicate p)

筛选,匹配符合规则的元素

distinct()

筛选,通过hashcode 和 equals去除重复元素

limit(long maxSize)

筛选,取不超过给定数量的元素

skip(long n)

筛选,跳过前n个元素,若流中元素不足n个则返回空流

map(Function<T> f)

映射,输入一个T类型的元素,返回一个R类型的元素;

如果返回的类型是也是Stream类型,会直接把Stream放入原Stream中

mapToDouble(ToDoubleFunction<T> f)

映射,输入一个T类型的元素,返回一个Double类型的元素

mapToLong(ToLongFunction<T> f)

映射,输入一个T类型的元素,返回一个Long类型的元素

flatMap(Function<T> f)

映射,输入一个T类型的元素,返回一个R类型的元素;

如果返回的类型是也是Stream类型,会把Stream中的元素取出放入原Stream中;

sorted()

排序,按自然顺序排序,产生一个新流

sorted(Comparator comp)

排序,按比较器排序,产生一个新流

如果只做中间操作,是不会产生任何执行的,比如:

//定义User类
public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        System.out.println("getAge 方法执行");
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

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

//测试类
public class Test1 {

    static List<User> userList = Arrays.asList(
            new User("A",20),
            new User("B",25),
            new User("C",30));

    public static void main(String[] args) {

        //需求:获取age>20的user
        Stream<User> userStream = userList.stream().filter(user -> user.getAge() > 20);
        System.out.println("运行结束");

    }
}

运行后输出如下,并未输出 getAge方法内的内容:

所以中间操作需要与终止操作一起使用,例如使用终止操作forEach方法进行输出。

示例一: filter(Predicate p) 的使用

public static void main(String[] args) {

    //需求:获取age>20的user
    userList.stream().filter(user -> user.getAge() > 20)
            .forEach(System.out::println);
    System.out.println("运行结束");

}

运行结果如下:

示例二: limit 的使用

public static void main(String[] args) {

    //需求:获取age>20的user
    userList.stream().filter(user -> user.getAge() > 20)
            .limit(1)
            .forEach(System.out::println);
    System.out.println("运行结束");

}

输出结果如下:

可以看出一旦达到limit的个数,遍历就会结束,不会继续遍历剩下的元素。

示例三: skip的使用

public static void main(String[] args) {

    //需求:获取age>20的user
    userList.stream()
            .skip(2)
            .forEach(System.out::println);
    System.out.println("运行结束");

}

输出结果如下:

示例四: distinct的使用

由于根据hashcode和equals去重复,所以User类需重写hashcode和equals方法才可去重

//定义User类,重写hashcode和equals方法
public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        System.out.println("getAge 方法执行");
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

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

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

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

//测试类
public class Test1 {

     //姓名C的User有3个重复的
    static List<User> userList = Arrays.asList(
            new User("A",20),
            new User("B",25),
            new User("C",30),
            new User("C",30),
            new User("C",30));

    public static void main(String[] args) {

        //需求:获取age>20的user
        userList.stream()
                .distinct()
                .forEach(System.out::println);
        System.out.println("运行结束");

    }
}

输出结果如下,只输出了一个姓名为C的用户,说明已经去重复:

示例五: map(Function<T> f) 的使用

public static void main(String[] args) {
    //需求:将数组中的每个元素转大写

    //1.使用lambda表达式实现Function接口
    Arrays.stream(new String[]{"aaa","bbb","ccc"})
            .map(str->str.toUpperCase())
            .forEach(System.out::println);

    System.out.println("-----------------------");

    //2.使用lambda方法引用实现Function接口
    Arrays.stream(new String[]{"aaa","bbb","ccc"})
            .map(String::toUpperCase)
            .forEach(System.out::println);
}

运行结果如下:

示例六: flatMap(Function<T> f) 的使用

与map的区别:

当map函数的返回值类型是也是Stream类型(Stream类型可以包含多个元素)时,

map会把 新Stream 直接 放入原Stream中,

flatMap会把 新Stream中的元素 一个个取出放入 原Stream中;

就好像是集合的add方法,flatMap就好像是集合的addAll方法,

示例:

public static void main(String[] args) {
    //需求:将数组中的每个元素拆分成单个字符输出

    //使用map函数,将toCharStream方法返回的 Stream<Character>作为一个元素 直接放到原Stream中
    Stream<Stream<Character>> outStream = Arrays.stream(new String[]{"aaa", "bbb"})
            .map(str -> toCharStream(str));
    //需要嵌套forEach才能完全遍历
    outStream.forEach(
            innerStream->innerStream.forEach(System.out::println)
    );

    System.out.println("------------------------------");

    //使用flatmap函数,将toCharStream方法返回的 Stream<Character> 里面的元素取出,放入到原 stream中
    Stream<Character> flatStream = Arrays.stream(new String[]{"aaa", "bbb", "ccc"})
            .flatMap(str -> toCharStream(str));
    //不用嵌套forEach
    flatStream.forEach(System.out::println);

}

//将一个字符串转为 Stream<Character>,可以把Stream也看成是一种集合,里面存的是字符串拆成的字符
public static Stream<Character> toCharStream(String str){
    List<Character> list = new ArrayList<>();
    for (Character ch :str.toCharArray()){
        list.add(ch);
    }
    return list.stream();
}

运行结果如下:

如果觉得不好理解,看这个List的例子是否可以理解:

public static void main(String[] args) {
    ArrayList outlist = new ArrayList();
    outlist.add("aaa");

    ArrayList inlist = new ArrayList();
    inlist.add("bbb");
    inlist.add("ccc");

    //使用add方法添加一个集合
    outlist.add(inlist);
    System.out.println("add方法添加inlist:"+outlist);

    System.out.println("------------------");
    outlist = new ArrayList();
    outlist.add("aaa");

    //使用addAll方法添加一个集合
    outlist.addAll(inlist);
    System.out.println("addAll方法添加inlist:"+outlist);
}

输出如下:

add方法(好比map方法)将整个inlist作为一个元素放入outlist中,因此存在中括号;

addAll方法(好比flatMap方法)将inlist中的元素取出放入outlist中,因此无中括号。

示例7: sorted() 的使用

自然排序

public static void main(String[] args) {
    Arrays.stream(new String[]{"ddd","bbb","aaa","ccc"})
            .sorted()
            .forEach(System.out::println);
}

结果如下:

示例8: sorted(Comparator comp) 的使用

按照年龄升序排列

public class Test1 {
    static List<User> userList = Arrays.asList(
            new User("A",30),
            new User("B",25),
            new User("C",28));

    public static void main(String[] args) {
        userList.stream()
                .sorted((o1,o2)->Integer.compare(o1.getAge(),o2.getAge()))
                .forEach(System.out::println);
    }
}

结果如下:

三,终止操作

方法

说明

allMatch(Predicate p)

流中元素是否全部匹配规则p,全部匹配返回true,否则false

anyMatch(Predicate p)

流中元素是否有一个匹配规则p,有匹配返回true,无则false

noneMatch(Predicate p)

流中元素是否全部不匹配规则p,

findFirst()

取流中第一个元素

findAny()

从流中任取一个元素

forEach(Consumer<T> con)

遍历并逐个消费元素

count()

统计流中元素个数

max(Comparator comp)

返回流中最大的元素

min(Comparator comp)

返回流中最小的元素

reduce

规约,将流中元素按照指定规则合并成一个元素

collect

收集,将流中元素收集到一个集合中

示例如下:

public class Test1 {

    static List<User> userList = Arrays.asList(
            new User("A",30),
            new User("B",25),
            new User("C",28));

    public static void main(String[] args) {
        //是否所有人都大于18岁
        boolean b1 = userList.stream()
                .allMatch(user -> user.getAge() > 18);
        System.out.println("是否所有人都大于18岁:"+b1);

        //是否有任意一个人小于18岁
        boolean b2 = userList.stream()
                .anyMatch(user -> user.getAge() < 18);
        System.out.println("是否有任意一个人小于18岁:"+b2);

        //是否所有人都不小于18岁,和allMatch是反的
        boolean b3 = userList.stream()
                .noneMatch(user -> user.getAge() < 18);
        System.out.println("是否所有人都不小于18岁:"+b3);

        //按年龄排序取第一个元素
        Optional<User> first = userList.stream()
                .sorted((o1, o2) -> Integer.compare(o1.getAge(), o2.getAge()))
                .findFirst();
        System.out.println("按年龄排序取第一个元素:"+first.get());


        //返回任意一个大于18岁的
        HashSet<User> set = new HashSet<>();
        HashSet<User> set2 = new HashSet<>();
        for(int i=0;i<10000;i++){
            //采用stream流
            Optional<User> any = userList.stream()
                    .filter(user->user.getAge()>18)
                    .findAny();
            set.add(any.get());

            //采用parallelStream流
            Optional<User> any2 = userList.parallelStream()
                    .filter(user->user.getAge()>18)
                    .findAny();
            set2.add(any2.get());
        }
        System.out.println("返回任意一个大于18岁的--stream流:"+set);
        System.out.println("返回任意一个大于18岁的--parallelStream:"+set2);

        //count的使用,统计流中元素个数
        long count = userList.stream()
                .filter(user -> user.getAge()>25)
                .count();
        System.out.println("统计超过25岁的人数:"+count);


        //max的使用,返回流中最大的元素
        Optional<User> max = userList.stream()
                .max((o1, o2) -> Integer.compare(o1.getAge(), o2.getAge()));
        System.out.println("年龄最大的人是:"+max);

        //min的使用,返回流中最小的元素
        Optional<Integer> minAge = userList.stream()
                .map(User::getAge)//只取出年龄放入stream中
                .min(Integer::compare);
        System.out.println("最小的岁数是:"+minAge);
    }
}

运行结果如下:

对于findAny(),从运行结果可以看出:

当使用stream流(串行流)时,findAny() 取首个元素;

当使用parallelStream流(并行流)时,findAny() 取得的元素不确定;

reduce示例如下:

public class Test1 {

    static List<User> userList = Arrays.asList(
            new User("A",30),
            new User("B",25),
            new User("C",28));

    public static void main(String[] args) {
        //reduce的使用,将流中元素按指定规则合并为一个元素
        //reduce第一种用法,传入默认值,那么结果一定不为空,所以返回值类型不是Optional
        Integer reduce1 = userList.stream()
                .map(User::getAge)//只取出年龄放入stream中
                .reduce(0, (x, y) -> x + y);
        System.out.println("对所有user的年龄进行累加,初始值为0,最终累计年龄:"+reduce1);

        //reduce第二种用法,不传入默认值,那么结果可能为空,所以返回值类型为Optional
        Optional<Integer> reduce2 = userList.stream()
                .map(User::getAge)//只取出年龄放入stream中
                .reduce(Integer::sum);//引用Integer.sum方法作为BinaryOperator接口的实现
        System.out.println("对所有user的年龄进行累加,无初始值,最终累计年龄:"+reduce2.get());
    }
}

运行结果如下:

collect 示例如下:

public class Test1 {

    static List<User> userList = Arrays.asList(
            new User("A",30,0),
            new User("B",15,1),
            new User("C",60,0));

    public static void main(String[] args) {
        //collect第一种用法,使用Collectors.toList
        List<Integer> collect1 = userList.stream()
                .map(User::getAge)//取出年龄
                .collect(Collectors.toList());//将每个年龄存入List集合中并返回List集合
        System.out.println("使用Collectors.toList:"+collect1);

        //collect第二种用法,使用Collectors.toSet
        Set<Integer> collect2 = userList.stream()
                .map(User::getAge)//取出年龄
                .collect(Collectors.toSet());//将每个年龄存入Set集合中并返回Set集合
        System.out.println("使用Collectors.toSet:"+collect2);

        //collect第三种用法,使用Collectors.toCollection,指定使用 HashSet 或 LinkedList
        HashSet<Integer> collect3 = userList.stream()
                .map(User::getAge)//取出年龄
                .collect(Collectors.toCollection(HashSet::new));
        System.out.println("使用Collectors.toCollection:"+collect3);

        //collect第四种用法,使用Collectors.toMap
        Map<String, Integer> collect4 = userList.stream()
                .collect(Collectors.toMap(User::getName, User::getAge));//取每个user对象的name做key,age做value存入map中,最后返回map
        System.out.println("使用Collectors.toMap:"+collect4);

        //collect第五种用法,取总数
        Long collect5 = userList.stream()
                .collect(Collectors.counting());
        System.out.println("使用Collectors.counting取总数:"+collect5);

        //collect第六种用法,求平均值
        Double collect6 = userList.stream()
                .collect(Collectors.averagingDouble(User::getAge));
        System.out.println("使用Collectors.averagingDouble求平均值:"+collect6);

        //collect第七种用法,求sum
        Double collect7 = userList.stream()
                .collect(Collectors.summingDouble(User::getAge));
        System.out.println("使用Collectors.summingDouble求总和:"+collect7);

        //collect第八种用法,求max
        Optional<User> collect8 = userList.stream()
                .collect(Collectors.maxBy((user1, user2) -> Integer.compare(user1.getAge(), user2.getAge())));
        System.out.println("使用Collectors.maxBy求年龄最大的用户:"+collect8.get());

        //collect第九种用法,求min
        Optional<Integer> collect9 = userList.stream()
                .map(User::getAge)
                .collect(Collectors.minBy(Integer::compare));
        System.out.println("使用Collectors.minBy求用户中最小的岁数:"+collect9.get());

        //collect第十种用法,groupby 分组
        Map<Integer, List<User>> collect10 = userList.stream()
                //按性别分组,返回一个Map,key是性别,value是该性别的User对象集合
                .collect(Collectors.groupingBy(User::getSex));
        System.out.println("使用Collectors.groupingBy按照性别分组:"+collect10);

        //collect第十一种用法,groupby 多级分组
        Map<Integer, Map<String, List<User>>> collect11 = userList.stream()
                //按性别分组,返回一个Map,key是性别,value是该性别的User对象集合
                .collect(Collectors.groupingBy(User::getSex, Collectors.groupingBy(user -> {
                    if (user.getAge() < 18) {
                        return "少年";
                    } else if (user.getAge() < 50) {
                        return "中年";
                    } else {
                        return "老年";
                    }
                })));
        System.out.println("使用Collectors.groupingBy先按照性别分组,再按照年龄段分组:"+collect11);

        //collect第十二种用法,groupby 分组
        Map<Boolean, List<User>> collect12 = userList.stream()
                //按性别分为 key=true 和 key=false 的两个组
                .collect(Collectors.partitioningBy(user -> user.getSex() == 0));
        System.out.println("使用Collectors.partitioningBy分为两个区:"+collect12);

        //collect第十三种用法,综合计算,可同时计算sum、平均值、count、max、min等
        DoubleSummaryStatistics dss = userList.stream()
                .collect(Collectors.summarizingDouble(User::getAge));
        System.out.println("使用Collectors.summarizingDouble 综合求值,sum="+ dss.getSum()+",平均值="
                +dss.getAverage()+",count="+dss.getCount()+",max="+dss.getMax()+",min="+dss.getMin());

        //collect第十四种用法,拼接字符串
        String collect14 = userList.stream()
                .map(User::getName)
                .collect(Collectors.joining(",","-前缀可不传-","-后缀可不传-"));
        System.out.println("使用Collectors.joining拼接字符串:"+collect14);
    }
}

运行结果如下:

使用Collectors.toList:[30, 15, 60]
使用Collectors.toSet:[60, 30, 15]
使用Collectors.toCollection:[60, 30, 15]
使用Collectors.toMap:{A=30, B=15, C=60}
使用Collectors.counting取总数:3
使用Collectors.averagingDouble求平均值:35.0
使用Collectors.summingDouble求总和:105.0
使用Collectors.maxBy求年龄最大的用户:User{name='C', age=60, sex=0}
使用Collectors.minBy求用户中最小的岁数:15
使用Collectors.groupingBy按照性别分组:{0=[User{name='A', age=30, sex=0}, User{name='C', age=60, sex=0}], 1=[User{name='B', age=15, sex=1}]}
使用Collectors.groupingBy先按照性别分组,再按照年龄段分组:{0={老年=[User{name='C', age=60, sex=0}], 中年=[User{name='A', age=30, sex=0}]}, 1={少年=[User{name='B', age=15, sex=1}]}}
使用Collectors.partitioningBy分为两个区:{false=[User{name='B', age=15, sex=1}], true=[User{name='A', age=30, sex=0}, User{name='C', age=60, sex=0}]}
使用Collectors.summarizingDouble 综合求值,sum=105.0,平均值=35.0,count=3,max=60.0,min=15.0
使用Collectors.joining拼接字符串:-前缀可不传-A,B,C-后缀可不传-

四,并行流

使用Fork/Join并行框架对流中元素进行处理,发挥多线程优势,提升效率。一般而言,如果是纯计算型(非IO型)处理,元素数量少于十万级用串行流更快,超过十万级并行流才可能更快,具体还是以实测为准,不要盲目使用并行流。

先了解Fork/Join框架:

Fork/Jion使用示例:计算start到end之间所有数字之和

import java.util.concurrent.RecursiveTask;

//从start到end之间所有数字求和
public class ForkJoinTest extends RecursiveTask<Long> {

    //拆分到每个Task中不超过10000个元素
    private static int THRESHOLD = 10000;

    private long start;
    private long end;

    public ForkJoinTest(long start, long end) {
        this.start = start;
        this.end = end;
    }

    public static void main(String[] args) {
        ForkJoinTest forkJoinTest = new ForkJoinTest(1,100000000);
        Long result = forkJoinTest.compute();
        System.out.println(result);
    }

    @Override
    protected Long compute() {
        long length = end - start;
        //小于THRESHOLD开始计算求和
        if(length<=THRESHOLD){
            long sum = 0;
            for(long i=start;i<=end;i++){
                sum = sum +i;
            }
            return sum;
        }else{
            //大于THRESHOLD,继续拆分任务
            long middle = (start+end)/2;
            ForkJoinTest left = new ForkJoinTest(start, middle);
            left.fork();

            ForkJoinTest right = new ForkJoinTest(middle+1, end);
            right.fork();
            return left.join() + right.join();
        }
    }
}

运行结果如下:

可以看出Fork/Join使用到了递归,对于普通开发人员来说,使用门槛比较高,java8使用并行流内部就是使用Fork/Join,开发人员不必再手写Fork/Join

用java8的并行流实现上述求和代码:

public static void main(String[] args) {
    long reduce = 
        //产生0-100000000的数字区间
        LongStream.rangeClosed(0, 100000000)
        //将流转为并行流,默认为串行流
        .parallel()
        //0为初始值,引用Long.sum方法作为BiFunction接口的实现
        .reduce(0, Long::sum);
    System.out.println(reduce);
}

Steam对象提供了parallel() 和 sequential()方法,可以切换并行流或串行流,也可以直接使用比如:

static List<User> userList = Arrays.asList(
            new User("A",30,0),
            new User("B",15,1),
            new User("C",60,0));

public static void main(String[] args) {
    List<Integer> collect1 = userList
            //创建流,默认是串行流
            .stream()
            //转为并行流
            .parallel()
            .map(User::getAge)//取出年龄
            .collect(Collectors.toList());//将每个年龄存入List集合中并返回List集合

    List<Integer> collect = userList
               //直接创建并行流
                .parallelStream()
                .map(User::getAge)//取出年龄
                .collect(Collectors.toList());//将每个年龄存入List集合中并返回List集合
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值