Java 8 stream流
它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用SQL执行的数据库查询。Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
Stream :非常方便精简的形式遍历集合实现 过滤、排序等。
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是一个泛型类,其中定义了两个泛型参数 T 和 R ,在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());