java8流的使用
基础数据
Student类
@Data
public class Student {
/** 学号 */
private String studentId;
/** 姓名 */
private String name;
/** 年龄 */
private int age;
/** 性别 m-男,w-女 */
private String sex;
/** 体重 */
private double weight;
/** 高度 */
private double height;
public Student(String studentId,String name,int age,String sex,double weight,double height){
this.studentId = studentId;
this.name = name;
this.age = age;
this.sex = sex;
this.weight = weight;
this.height = height;
}
}
公共的资源(自定义List和Map)
/**
* @Description 公共的资源
* @Author lcy
* @Date 2020/4/14 16:53
*/
public class Util {
public static Map<String,Student> studentMap = new HashMap<>();
public static List<Student> studentList = new ArrayList<>();
static{
String[] nameArr = {"张三","李四","王五","赵六","钱七","孙八","周九","吴十","郑十一"};
int[] ageArr = {20,21,22,23,24,25,26};
int height = 160;
int weight = 120;
for (int i = 0; i < 9; i++) {
String studentId = (100 + i * ageArr.length * 10) + "";
int ageIndex = i % (ageArr.length-1);
String sex = i % 2 == 0 ? "w" : "m";
int newHeight = height + i % 3 * 10;
int newWeight = weight + i % 3 * 10;
Student student = new Student(studentId,nameArr[i],ageArr[ageIndex]
,sex,newWeight,newHeight);
studentList.add(student);
studentMap.put(student.getStudentId(),student);
}
}
}
一、筛选(中间操作)
使用谓词筛选,filter筛选条件范围内的数据,distinct去重,limit截断流。skip相反的是跳过元素,如果
Util.studentList.stream()
//筛选年龄大于21岁的
.filter(student -> student.getAge() > 21)
//去重
.distinct()
//截断,只取前4个
.limit(4)
//跳过前面两个
.skip(2)
//转成集合
.collect(toList());
二、映射(中间操作)
使用map会接受一个函数作为一个参数,这个参数会应用到(list)每个元素上,映射成一个新的元素。
Util.studentList.stream()
//将结果由Stream<Student>转成Stream<String>
.map(Student::getName)
//转成集合
.collect(toList());
使用flatmap将流进行扁平化,使用flatMap方法的效果是,map以后各个元素并不是分别映射成一个流,而是映射成流的内容。所 有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流。
List<Integer> bbb = Arrays.asList(1,2,3);
List<Integer> ccc = Arrays.asList(4,5);
bbb.stream()
//如果用这里用map,流的数据为Stream<Integer[]>,用flatmap就变成Stream<Integer>
.flatMap(bbbb -> ccc.stream()
//能够被3整除的数组
.filter(cccc -> (bbbb + cccc) % 3 == 0)
//组成新的数组
.map(cccc -> new int[]{bbbb,cccc}))
//输出
.forEach(ab -> System.out.println(Arrays.toString(ab)));
三、查找和匹配(终端操作)
allMatch是否所有元素都匹配,noneMatch是否没有元素匹配,anyMatch是否最少一个元素匹配,最终都返回一个boolean类型的值
Util.studentList.stream()
//判断是否有人的年龄大于100,如果列表里有一个,就是true
.anyMatch(student -> student.getAge() > 100);
Util.studentList.stream()
//判断是否所有人的身高都大于120
.allMatch(student -> student.getHeight() > 120);
Util.studentList.stream()
//判断是否列表里没有一个人的身高大于120
.noneMatch(student -> student.getHeight() > 120);
findFirst()是返回第一个元素。 findAny()一般来说如果使用的是串行stream流的形式,与findFirst一样返回第一个元素,如果使用的是并行流parallelStream,从当前流的列表里获取一个随机元素返回 这两个操作返回的都是Optional<T>,利用Optional,避免和null检查相关的bug
Util.studentList.stream().filter(student -> student.getAge() > 19).forEach(System.out::println);
System.out.println("-------------------");
Optional<Student> any = Util.studentList
//并行流,多个中间操作并行操作,stream是串行流,如果用stream,findAny返回的结果与findFirst一样都是第一个元素
.parallelStream()
.filter(student -> student.getAge() > 19)
.findAny();
any.ifPresent(System.out::println);
四、归约(终端操作)
使用reduce()将所有元素反复结合起来(可以理解为运算),得到一个值。例如元素的求和
Double reduce = Util.studentList.stream()
.map(Student :: getHeight)
//0是初始值,第一个元素计算的时候与初始值计算
//.reduce((double)0,(a,b) -> a + b);
//另外一种求和方式
.reduce((double)0,Double::sum);
五、排序、最大值、最小值
sorted排序,默认从小到大排序,max获取最大值,min获取最小值,如果最大值或最小值有多个,只会返回一个值
Util.studentList.stream()
//将结果由Stream<Student>转成Stream<String>
.map(Student :: getStudentId)
//默认排序方式 从小到大
.sorted()
.forEach(System.out::println);
//转成集合
//.collect(toList());
Util.studentList.stream()
//等于 map和sort结合
.sorted(comparing(Student::getStudentId))
.forEach(System.out::println);
System.out.println("min--------------------------------max");
Util.studentList.stream()
.filter(student -> student.getAge()>30)
//将结果由Stream<Student>转成Stream<String>
.map(Student :: getStudentId)
//获取最大学号
.max(String::compareTo)
//如果值不为空执行函数操作
.ifPresent(System.out::println);
Util.studentList.stream()
//将结果由Stream<Student>转成Stream<Integer>
.map(Student :: getAge)
//获取最小学号
.min(Integer::compareTo)
.ifPresent(System.out::println);
六、数值流(避免装箱和拆箱)
将Integer的流类型映射成int,这样就避免装箱。使用boxed将int的流转成Integer。
Util.studentList.stream()
//将结果由Stream<Student>转成IntStream
.mapToInt(Student :: getAge)
.max();
//如果max为空的情况下,给定一个值设置默认值,并且返回
int maxInt = max.orElse(1);
Util.studentList.stream()
//将结果由Stream<Student>转成IntStream
.mapToInt(Student :: getAge)
//将结果由IntStream转成Stream<Integer>
.boxed()
.max(Integer::compareTo);
数值范围 生成范围内数值的流,根据IntStream的静态方法rangeClosed(start,end)和range(start,end),start参数是起始值,end是结束值。 range的范围是[start,end),rangeClosed的范围是[start,end]
IntStream range = IntStream
.range(0,100);
IntStream rangeClosed = IntStream
.rangeClosed(0,100);
通过数值流IntStream、DoubleStream、LongStream的无限流生成数列。 IntSupplier匿名类可以通过字段定义状态,而状态又可以用getAsInt方法来修改。这是一个副作用的例子。Lambda都是没有副作用的;它们没有改变任何状态。使用iterate的方法则是纯粹不变的:它没有修改现有状态,但在每次迭代时会创建新的元组
Stream<Integer> iterate = Stream
//创建无限流
.iterate(0,a -> a + a)
//必须截断,否则会一直循环创建
.limit(10);
Stream
//生成菲波那切数列
.iterate(new int[]{0,1},n -> new int[]{n[1],n[0] + n[1]})
.limit(10).forEach((a)-> System.out.println(Arrays.toString(a)));
IntStream
// 创建无限流 只生成1
//.generate(() -> 1)
//生成带函数的斐波那契数列
.generate(new IntSupplier() {
private int a = 0;
private int b = 1;
@Override public int getAsInt(){
int temp = a;
a = b;
b = temp + b;
return b;
}
})
.limit(5).forEach(System.out::println);
七、并行流
以下列表是一些流的数据源对于并行的可分解性
数据源 | 可分解性 |
---|---|
ArrayList | 极佳 |
LinkedList | 差 |
IntStream.range | 极佳 |
Stream.iterate | 差 |
HashSet | 好 |
TreeSet | 好 |
并行——使用流还是CompletableFutures?
- 如果你进行的是计算密集型的操作,并且没有I/O,那么推荐使用Stream接口,因为实现简单,同时效率也可能是最高的(如果所有的线程都是计算密集型的,那就没有必要创建比处理器核数更多的线程)。
- 如果你并行的工作单元还涉及等待I/O的操作(包括网络连接等待),那么使用CompletableFuture灵活性更好,你可以像前文讨论的那样,依据等待/计算,或者W/C的比率设定需要使用的线程数。这种情况不使用并行流的另一个原因是,处理流的流水线中如果发生I/O等待,流的延迟特性会让我们很难判断到底什么时候触发了等待。
九、流问题排查
如果使用for-each是会恢复流的正常使用,无法判断异常的出处,所以使用peek,在每次操作前进行一次操作
Util.studentList.stream()
//插入一个打印的动作
.peek(student -> System.out.println("peek1"))
.map(Student::getStudentId)
//插入一个打印的动作
.peek(student -> System.out.println("peek2"))
.collect(toList())
//forEach会恢复流的运行。出现异常无法顺利调试
.forEach(System.out::println);
十、终端筛选
使用groupingBy可以筛选将list转成map<k,List>、map<k,Set>等,根据groupingBy和其它收集器转换成想要的数据。 toSet方法返回的Set是什么类型并没有任何保证,通过自定义的toCollection(HashSet::new)可以得到解决
Util.studentList.stream().collect(
//两层分组,第一层,把性别分组
groupingBy(Student :: getSex
//把年龄再分组
,groupingBy(Student :: getAge))
).forEach((k,v) -> System.out.println(k + ":" + v));
Util.studentList.stream().collect(
//把性别分组,不同性别的高度求和
groupingBy(Student :: getSex
//把年龄再分组,返回的是Map<String,List<Studen>>
,summingDouble(Student :: getHeight)))
.forEach((k,v) -> System.out.println(k + ":" + v));
System.out.println("---------------------------");
Util.studentList.stream().collect(
//把性别分组,不同性别的高度求和
groupingBy(Student :: getSex,
//把年龄再分组,返回的是Map<String,Set<Double>>
//toset没有保证set的类型
//mapping(Student :: getHeight,toSet())))
mapping(Student::getHeight,toCollection(HashSet ::new))))
.forEach((k,v) -> System.out.println(k + ":" + v));
Util.studentList.stream().collect(
//根据性别分类,男的为true,女的为false
partitioningBy((student) -> student.getSex().equals("m")))
.forEach((k,v) -> System.out.println(k + ":" + v));
Util.studentList.stream().collect(
//根据性别分类,男的为true,女的为false
partitioningBy((student) -> student.getSex().equals("m"),
//二次分组
groupingBy(Student::getAge)))
.forEach((k,v) -> System.out.println(k + ":" + v));
分区函数是分组函数中特殊的一种,根据boolean值来进行筛选分区
Util.studentList.stream().collect(
//根据性别分类,男的为true,女的为false
partitioningBy((student) -> student.getSex().equals("m")))
.forEach((k,v) -> System.out.println(k + ":" + v));
通过终端minBy和maxBy筛选最大值和最小值、summiingInt、summingLong、summingDouble求和,与max和min一样。
Optional<Student> collect = Util.studentList.stream()
//筛选最大年龄的人,首先通过comparing实现对比,再由maxBy筛选,如果最大值或最小值有多个,只会返回一个值
.collect(minBy(comparing(Student :: getAge)));
//.ifPresent(System.out::println);
//求年龄的总和
Util.studentList.stream().collect(summingInt(Student::getAge))
//求平均年龄
Util.studentList.stream().collect(averagingInt(Student::getAge));
终端通过joining可以将数据连串起来。还可以根据的参数进行分割开
//连接字符串,将名字用,隔开
Util.studentList.stream().map(Student :: getName).collect(joining(","));
collectingAndThen用于转换函数返回的类型,包裹另一个收集器,对其结果应用转换函数
int howManyDishes =Util.studentList.stream().collect(collectingAndThen(toList(), List::size));
十一、自定义终端收集器ToListCollector(T)
import static java.util.stream.Collector.Characteristics.*;
/**
* @Description TODO
* @Author lcy
* @Date 2020/4/21 17:57
*/
public class ToListConllector<T> implements Collector<T,List<T>,List<T>> {
/** 创建集合操作的起始点 */
@Override public Supplier<List<T>> supplier(){
//创建一个ArrayList实例装载数据
return ArrayList ::new;
}
/** 遍历过的项目处理。累加 */
@Override public BiConsumer<List<T>,T> accumulator(){
//添加数据
return List::add;
}
/** 恒等函数 */
@Override public Function<List<T>,List<T>> finisher(){
return Function.identity();
}
/** 修改第一个累加器,即 BiConsumer()方法,与第二个累加器合并*/
@Override public BinaryOperator<List<T>> combiner(){
return (list1,list2)->{
list1.addAll(list2);
//返回修改后的第一个累加器
return list1;
};
}
/** 为收集器添加 IDENTITY_FINISH,CONCURRENT的标志*/
@Override public Set<Characteristics> characteristics(){
return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH,CONCURRENT));
}
}
调用实例
List<Student> collect = Util.studentList.stream().collect(new ToListConllector<>());
//等同于
List<Student> collect = Util.studentList.stream().collect(
ArrayList ::new,
List::add,
List::addAll);