Java 8 stream流

它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用SQL执行的数据库查询。Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

Stream :非常方便精简的形式遍历集合实现 过滤、排序等。 

stream nonematch过滤交集数据 stream过滤集合_jvm

1. 使用Stream流处集合去重

List<UserEntity> userEntityList = new ArrayList<>();
        userEntityList.add(new UserEntity("zs",20));
        userEntityList.add(new UserEntity("ls",50));
        userEntityList.add(new UserEntity("zs",20));
        userEntityList.add(new UserEntity("zl",200));
        Set<UserEntity> collect = userEntityList.stream().collect(Collectors.toSet());
        collect.forEach(t -> System.out.println(t));
        // 没有实现去重效果,因为没有给 UserEntity 重新 hashCode与equals方法!!
UserEntity{name='zl', age=200}
UserEntity{name='ls', age=50}
UserEntity{name='zs', age=20}
UserEntity{name='zs', age=20}

没有实现去重效果,因为没有给 UserEntity 重新 hashCode与equals方法!!

添加hashCode与equals方法

package com.fan.bean;

public class UserEntity {
    private String name;
    private Integer age;

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

    public String getName() {
        return name;
    }

    public Integer getAge() {
        return age;
    }

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

    @Override
    public boolean equals(Object obj) {
        if(obj instanceof UserEntity){
            return name.equals(((UserEntity) obj).name) && age == ((UserEntity) obj).age;
        }else {
            return false;
        }
    }
    @Override
    public int hashCode(){
        return name.hashCode();
    }
}

成功去重 

UserEntity{name='zl', age=200}
UserEntity{name='ls', age=50}
UserEntity{name='zs', age=20}

set集合底层依赖与map集合实现防重的key,map集合底层基于 equals方法 和 hashCode防重。

equals 默认为 比较俩个对象的内存地址是否一致。但String的equals()方法是进行内容比较,而不是单纯的引用比较。因为Stirng重写了 equals()方法。

基本数据类型内存是在栈里面,然后int a =5;再创建一个 int b = 5; 这个b会先去栈里面找是否有5的,如果有就是指向同一个地址,所以基本数据类型 == 就是相当于比较的是内容

补充知识:

在JVM中,内存分为堆内存跟栈内存。他们二者的区别是: 当我们创建一个对象(new Object)时,就会调用对象的构造函数来开辟空间,将对象数据存储到堆内存中,与此同时在栈内存中生成对应的引用,当我们在后续代码中调用的时候用的都是栈内存中的引用。还需注意的一点,基本数据类型是存储在栈内存中。
参考:优美的讲解equals和==的区别_MrBoringBigFish的博客-CSDN博客

在Java中比较的推荐方法:

1. 对象域,使用equals方法 。
2. 类型安全的枚举,使用equals或== 。
3. 可能为null的对象域 : 使用==null 和 equals 。
4. 数组域 : 使用 Arrays.equals 。
5. 除float和double外的原始数据类型(int,byte等) : 使用 == 。
6. float类型: 使用Float.foatToIntBits转换成int类型,然后使用==。
7. double类型: 使用Double.doubleToLongBit转换成long类型,然后使用==。
其中6,7参考java中的对应的包装类实现:


public boolean equals(Object obj) {
    return (obj instanceof Float)
           && (floatToIntBits(((Float)obj).value) == floatToIntBits(value));
    }
}


参考:Java中的equals()方法_changshuchao的博客-CSDN博客_javaequals

 2. 将list集合转换为 map

/**
         * 将list集合转换为 map
         * list 集合只有元素值 key,转map集合需要指定 key - value 对象, name 为属性,user 为对象
         */
        Stream<UserEntity> stream = userEntityList.stream();
        /**
         * 在函数式编程之前我们定义一组操作首先想到的是定义一个方法,然后指定传入参数,返回我们需要的结果。
         * 函数式编程的思想是先不去考虑具体的行为,而是先去考虑参数,具体的方法我们可以后续再设置
         */
        /**
         * 可以看作Collectors.toMap(key,value)
         * key 为 new Function<UserEntity, String>
         * value 为 new Function<UserEntity, UserEntity>
         * 而单独的一个 new Function<UserEntity, UserEntity>() 即为 Function<T, R>
         * T代表输入参数,R代表返回的结果。
         * 所以,apply 方法中的 UserEntity 为入参, 返回值类型为 String / UserEntity (key,value)
         */
        Map<String, UserEntity> collect2 = stream.collect(Collectors.toMap(new Function<UserEntity, String>() {
            @Override
            public String apply(UserEntity userEntity) {
                return userEntity.getName();
            }
        }, new Function<UserEntity, UserEntity>() {
            @Override
            public UserEntity apply(UserEntity userEntity) {
                return userEntity;
            }
        }));
        // 精简写法
        Map<String, UserEntity> collects = stream.collect(Collectors.toMap(
                userEntity -> userEntity.getName(), userEntity -> userEntity));

想要看懂这里,需要了解一下 Function<T, R> 接口:

Function是一个泛型类,其中定义了两个泛型参数 和 ,在Function中,T代表输入参数,R代表返回的结果。而 apply(T t) 方法具体返回的结果取决于传入的lambda表达式。例如:

public void test(){
    Function<Integer,Integer> test=i->i+1;
    Integer apply = test.apply(5); // 返回 apply = 6
}

我们先不去考虑具体的行为,而是先去考虑参数,定义了 function入参为 Integer ,返回参数为 Integer,然后写具体的行为,i+1,i为入参,也就是 apply(5)。

所以,我们可以将 Collectors.toMap(new Function<UserEntity, String> ,new Function<UserEntity, UserEntity>)  看作为将 Collectors.toMap(key,value) 

key 为 new Function<UserEntity, String>  

value 为 new Function<UserEntity, UserEntity> 

而单独的一个 new Function<UserEntity, UserEntity>() 即为 Function<T, R> T代表输入参数,R代表返回的结果。 

所以,apply 方法中的 UserEntity 为入参, 返回值类型为 String / UserEntity (即为 key,value )

注意:Stream只能被消费一次,当其调用了终止操作后便说明其已被消费掉了。 如果还想重新使用,可考虑在原始数据中重新获得。不懂什么意思的可以执行我上面的代码看一下。

补充:可以参考Function函数式接口详解:

Java8中Function函数式接口详解及使用_LifeIsForSharing的博客-CSDN博客_java function函数的用法

遍历:

// 遍历
        collects.forEach(new BiConsumer<String, UserEntity>() {
            @Override
            public void accept(String s, UserEntity userEntity) {
                System.out.println("s:" + s + ",:" + userEntity.toString());
            }
        });
        // 遍历精简写法
        collects.forEach((name,user) -> System.out.println(user.toString()));

遍历使用到了 new BiConsumer<String, UserEntity> 方法,同理,String, UserEntity 为入参,accept 方法作用是对给定的参数执行操作。

3. Stream计算求和

List<UserEntity> userEntityList = new ArrayList<>();
        userEntityList.add(new UserEntity("zs",20));
        userEntityList.add(new UserEntity("ls",50));
        userEntityList.add(new UserEntity("zl",200));
        Stream<UserEntity> stream = userEntityList.stream();
        /***
         * new BinaryOperator<UserEntity> UserEntity 为操作数和结果的数据类型( <T> )
         * apply方法 接收两个参数,产生一个结果,只是它的三个参数都是同一个数据类型
         */
        Optional<UserEntity> reduce = stream.reduce(new BinaryOperator<UserEntity>() {
            @Override
            public UserEntity apply(UserEntity userEntity, UserEntity userEntity2) {
                userEntity.setAge(userEntity.getAge() + userEntity2.getAge());
                return userEntity;
            }
        });
        // 精简版
        Optional<UserEntity> reduce2 = stream.reduce((userEntity, userEntity2) -> {
            userEntity.setAge(userEntity.getAge() + userEntity2.getAge());
            return userEntity;
        });

这里 BinaryOperator<T> 继承了 BiFunction<T,T,T> 接口,apply方法其实是重写BiFunction中的。

public interface BinaryOperator<T> extends BiFunction<T,T,T> {

apply方法 接收两个参数,产生一个结果,只是它的三个参数都是同一个数据类型。 

这里读者不必过于纠结为什么是俩个 UserEntity 类型的,返回结果也是 UserEntity,但能实现求和操作,这里底层肯定是依次相加再传参相加,最终返回。感兴趣的同学可以自行研究。

4. 查最大值最小值

Stream<UserEntity> stream = userEntityList.stream();
        // min、max
        Optional<UserEntity> max = stream.min(new Comparator<UserEntity>() {
            @Override
            public int compare(UserEntity o1, UserEntity o2) {
                return o1.getAge()-o2.getAge();
            }
        });
        // 精简版
        Optional<UserEntity> max2 = stream.min((o1, o2) -> o1.getAge()-o2.getAge());
        // 另一种写法,先写出来,后面说
        Optional<UserEntity> max3 = stream.min(Comparator.comparingInt(UserEntity::getAge));
        
        System.out.println(max.get());

现在是不是可以理解 new Comparator 和精简写法啦

5. XXXMatch 匹配 

anyMatch表示,判断的条件里,任意一个元素成功,返回true

allMatch表示,判断条件里的元素,所有的都是,返回true

noneMatch跟allMatch相反,判断条件里的元素,所有的都不是,返回true

Stream<UserEntity> stream = userEntityList.stream();
        // anyMatch allMatch noneMatch
        boolean b = stream.anyMatch(new Predicate<UserEntity>() {
            @Override
            public boolean test(UserEntity userEntity) {
                // 具体执行
                return "zs".equals(userEntity.getName());
            }
        });
        // 精简写法
        stream.anyMatch(userEntity -> "zs".equals(userEntity.getName()));
        System.out.println(b);

到目前为止,对 我们先不去考虑具体的行为,而是先去考虑参数,这句话是不是有更深刻的理解了,以 anyMatch 为例,我们先不考虑具体的行为 (具体实现逻辑),而是先定义入参(UserEntity )和返回类(boolean)型,有了入参和返回类型再去进行 具体实现!

6. Stream filter 过滤器

List<UserEntity> userEntityList = new ArrayList<>();
        userEntityList.add(new UserEntity("zs",20));
        userEntityList.add(new UserEntity("ls",50));
        userEntityList.add(new UserEntity("ls",20));
        userEntityList.add(new UserEntity("zl",200));
        Stream<UserEntity> stream = userEntityList.stream();
        Stream<UserEntity> userEntityStream = stream.filter(new Predicate<UserEntity>() {
            @Override
            public boolean test(UserEntity userEntity) {
                return "ls".equals(userEntity.getName()) && userEntity.getAge() > 20;
            }
        });
        userEntityStream.forEach(userEntity -> System.out.println(userEntity.toString()));
        
        // 精简写法
        stream.filter(userEntity ->
                "ls".equals(userEntity.getName()) && userEntity.getAge() > 20).
                forEach(userEntity -> System.out.println(userEntity.toString()));

 7. Stream sorted 排序

List<UserEntity> userEntityList = new ArrayList<>();
        userEntityList.add(new UserEntity("zs",20));
        userEntityList.add(new UserEntity("ls",50));
        userEntityList.add(new UserEntity("ls",20));
        userEntityList.add(new UserEntity("zl",200));
        Stream<UserEntity> stream = userEntityList.stream();
        stream.sorted(new Comparator<UserEntity>() {
            @Override
            public int compare(UserEntity o1, UserEntity o2) {
                return o1.getAge() -o2.getAge();
            }
        }).forEach(userEntity -> System.out.println(userEntity.toString()));
        //精简写法
        stream.sorted((o1, o2) -> o1.getAge() -o2.getAge()).forEach(userEntity -> 
                System.out.println(userEntity.toString()));

8. Stream limit和skip

Limit 从头开始获取

Skip 就是跳过

List<UserEntity> userEntityList = new ArrayList<>();
        userEntityList.add(new UserEntity("zs",20));
        userEntityList.add(new UserEntity("ls",50));
        userEntityList.add(new UserEntity("ls",20));
        userEntityList.add(new UserEntity("zl",200));
        Stream<UserEntity> stream = userEntityList.stream();
        // 相当于 limit 2,2
        stream.skip(2).limit(2).forEach(userEntity -> System.out.println(userEntity.toString()));

混合使用案例:对数据流的数据实现降序排列、且名称包含zl 获取前两位

List<UserEntity> userEntityList = new ArrayList<>();
        userEntityList.add(new UserEntity("zs",20));
        userEntityList.add(new UserEntity("ls",50));
        userEntityList.add(new UserEntity("ls",20));
        userEntityList.add(new UserEntity("zl",200));
        userEntityList.add(new UserEntity("zl",300));
        userEntityList.add(new UserEntity("zl",100));
        Stream<UserEntity> stream = userEntityList.stream();
        //要求:对数据流的数据实现降序排列、且名称包含zl 获取前两位
        stream.filter(userEntity -> userEntity.getName().contains("zl")).
                sorted((o1,o2)-> o1.getAge()-o2.getAge()).limit(2).
                forEach(userEntity -> System.out.println(userEntity.toString()));

9. 并行流与串行流区别

串行流:单线程的方式操作; 数据量比较少的时候。

并行流:多线程方式操作;数据量比较大的时候,

原理:

Fork join 将一个大的任务拆分n多个小的子任务并行执行,最后在统计结果,有可能会非常消耗cpu的资源,确实可以提高效率。

注意:数据量比较少的情况下,不要使用并行流。

Instant star = Instant.now();
//        long num = 0;
//        // 使用单线程计算 五百亿求和耗费的时间为: 超过一分钟了,不等了,电脑风扇呜呜响
//        for(int i=0;i<=50000000000L;i++){
//            num+= 1;
//        }
//        System.out.println(num);
        LongStream longStream = LongStream.rangeClosed(0,50000000000L);
        // 使用并行流计算 五百亿求和耗费的时间为:3968
        OptionalLong re = longStream.parallel().reduce(new LongBinaryOperator() {
            @Override
            public long applyAsLong(long left, long right) {
                return left+right;
            }
        });
        System.out.println(re.getAsLong());
        Instant end = Instant.now();
        System.out.println("耗时:" + Duration.between(star,end).toMillis());