常用的steam创建方式
由集合创建
Java8 中的 Collection 接口经扩展,提供了两个获取流的default方法
- default Stream stream():返回一个顺序流
- default Stream parallelStream():返回一个并行流
List<Integer> intList=Arrays.asList (
1,2,3,4,5
);
Stream<Integer> stream = intList.stream ();
Stream<Integer> integerStream = intList.parallelStream ();
由数组创建
Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:
- static Stream stream(T[] array): 返回一个流
- 重载形式,能够处理对应基本类型的数组:
public static IntStream stream(int[] array)
public static LongStream stream(long[] array)
public static DoubleStream stream(double[] array)
例如: int[] intArray = {1,2,3}; IntStream stream = Arrays.stream(intArray);
将数组转换成List
核心就是 将基本数据类型数组转为包装类在转list, JDK1.8提供了stream流的方式 基本数据类型只支持 int long double
int[] i={1,1,5,6,8,9,4,3,3};
// boxed() 装箱操作
Arrays.stream (i).boxed ().collect (Collectors.toList ()).forEach (System.out :: println);
int[] i = {1, 2, 3};
IntStream stream = Arrays.stream(i);//转为stream流
//stream.forEach(System.out::println); //1 2 3
Stream<Integer> boxed = stream.boxed(); //装箱为Integer
List<Integer> collect = boxed.collect(Collectors.toList()); //收集
List<Integer> list = Arrays.stream(i).boxed().collect(Collectors.toList());
System.out.println(collect);
由值创建
可以使用静态方法 Stream.of(), 通过显示值 创建一个流。它可以接收任意数量的参数。
- public static Stream of(T... values) : 返回一个流。
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8);
由函数创建(创建无限流)
可以使用静态方法 Stream.iterate() 和 Stream.generate()创建无限流。
- 迭代 public static Stream iterate(final T seed, final UnaryOperator f)
- 生成 public static Stream generate(Supplier s)
Stream.generate(Math::random).limit(5).forEach(System.out::print);
List<Integer> collect = Stream.iterate(0,i -> i + 1).limit(5).collect(Collectors.toList());
注意:使用无限流一定要配合limit截断,不然会无限制创建下去。
Steam内置方法
Stream的操作分为两大类,一类为中间操作,一类为终端操作。
中间操作:返回值仍然为一个流,不会消耗流
终端操作:返回最终结果;终端操作会消耗掉流,使之不再可用
准备工作
- 创建Animal类
package com.stream.enity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Animal{
/**
* 种类
*/
String type;
/**
* 名称
*/
String name;
/**
* 年龄
*/
int age;
/**
* 颜色
*/
String color;
}
- 创建Animal类的集合
private static List<Animal> catList= Arrays.asList (
new Animal ("金毛","旺财",5,"白色"),
new Animal ("泰迪","来福",1,"黄色"),
new Animal ("拉布拉多","平安",2,"黑白花色"),
new Animal ("阿拉斯加","多多",4,"白色"),
new Animal ("阿拉斯加","多多",2,"白色"),
new Animal ("阿拉斯加","多多",4,"黑色"),
new Animal ("二哈","豆豆",3,"黑色"),
new Animal ("哈士奇","豆豆",3,"黑色")
);
方法测试
最常用的方法,将集合转换成流 stream() / parallelStream()
Stream<Animal> stream = catList.stream ();
Stream<Animal> animalStream = catList.parallelStream ();
stream.collect() 终
将流中的元素收集到集合中(将流转换为集合)
在Stream中有一个方法叫做collect,可以将流中的元素收集到集合(将流转成集合) R collect(Collector collector):参数collector表示将数据收集到哪种集合。
Collector是一个接口,我们要使用这个接口的实现类对象,这个接口的实现类对象不是由我们去创建的,而是通过 工具类获取,获取Collector的工具类叫做Collectors
Collectors中获取Collector的方法: static Collector toList():通过该方法获取到的Collector对象表示将数据收集到List集合。 static Collector toSet():通过该方法获取到的Collector对象表示将数据收集到Set集合。
收集到数组中
将收集的数据流转化为Collection对象
Collectors.toCollection() 将数据转换成Collection,只要是Collection的实现都可以,例如ArrayList,HashSet,该方法能够接受一个Collection对象
ArrayList<Integer> collectList = Stream.of (1 , 2 , 3 , 4 , 5 , 3 , 1).collect (Collectors.toCollection (ArrayList :: new));
HashSet<Integer> collectSet = Stream.of (1 , 2 , 3 , 4 , 5 , 3 , 1).collect (Collectors.toCollection (HashSet :: new));
collectList.stream ().forEach (System.out :: print);
System.out.println ("\n");
collectSet.stream ().forEach (System.out :: print);
转化为map对象
Map<String, Animal> collectMap = catList.stream ().collect (Collectors.toMap (Animal :: getName , Function.identity ()));
Function.identity ()
当使用 Stream 时,要将它转换成其他容器或 Map 。这时候,就会使用到 Function.identity()。
Function 是一个接口,Function.identity() 返回一个输出跟输入一样的Lambda表达式对象,等价于形如 t -> t 形式的 Lambda 表达式。
/**
* Returns a function that always returns its input argument.
*
* @param <T> the type of the input and output objects to the function
* @return a function that always returns its input argument
*/
static <T> Function<T, T> identity() {
return t -> t;
}
应用场景:
下面的代码中,Task::getTitle 需要一个 task 并产生一个仅有一个标题的 key 。
task -> task 是一个用来返回自己的 lambda 表达式,根据上例,可以得知会返回一个 task 。
private static Map<String, Task> taskMap(List<Task> tasks) {
return tasks.stream().collect(toMap(Task::getTitle, task -> task));
}
可以使用 Function 接口中的默认方法 identity 来让上面的代码代码变得更简洁明了、传递开发者意图时更加直接,下面是采用 identity 函数的代码。
import static java.util.function.Function.identity;
private static Map<String, Task> taskMap(List<Task> tasks) {
return tasks.stream().collect(toMap(Task::getTitle, identity()));
}
去重 distinct()
去除重复元素,方法内部通过类的 equals 方法判断两个元素是否相等
//去除重复元素(需要修改equals方法)
catList.stream ().distinct ().forEach (System.out :: println);
排序 sorted
如果流中的元素的类实现了 Comparable 接口,即有自己的排序规则,那么可以直接调用 sorted() 方法对元素进行排序,如 Stream
反之, 需要调用 sorted((T, T) -> int) 实现 Comparator 接口
//降序排列
catList.stream ().sorted ((item1,item2) ->item2.getAge () - item1.getAge ()).forEach (System.out :: println);
//升序排列
catList.stream ().sorted ((item1,item2) ->item1.getAge () - item2.getAge ()).forEach (System.out :: println);
//升序排列
catList.stream ().sorted (Comparator.comparingInt (Animal :: getAge)).forEach (System.out :: println);
基于limit()实现数据截取
可以指定要获取的前几条数据
//取前2条数据
catList.stream ().limit (2).forEach (System.out :: println);
基于skip()实现数据跳过
用于指定从第几条开始截取,之前的全部跳过
//跳过第一条数据,向后取两条数据
catList.stream ().skip (1).limit (2).forEach (System.out :: println);
过滤函数 filter(T -> boolean)
保留 boolean 为 true 的元素
//查询所有年龄为3岁的动物
catList.stream ().filter (item -> item.getAge () == 3).forEach (System.out :: println);
查找匹配元素 findFirst/findAny
findFirst 用于返回查找到的第一份匹配数据
findAny 用于返回任意一个匹配数据,查到任意一个即结束。fundAny在并行流中特别有效
Optional<Animal> duoduo = catList.stream ().filter (item -> item.getName ().contains ("多多")).findFirst ();
Optional<Animal> duoduo2 = catList.stream ().filter (item -> item.getName ().contains ("多多")).findAny ();
System.out.println (duoduo.get ());
System.out.println (duoduo2.get ());
匹配函数 anyMatch/allMatch/noneMatch
anyMatch/allMatch/noneMatch 只返回boolean值,不返回具体匹配数据
接口定义:
boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
anyMatch表示任意匹配(or);
allMatch表示全部匹配(and);
noneMatch表示不匹配(not)。
// anyMatch 任意匹配
System.out.println (catList.stream ().anyMatch (a -> a.getAge () == 6)?"存在6岁的宠物":"不存在6岁的宠物");
// allMatch 全部匹配
System.out.println (catList.stream ().allMatch (a -> a.getAge () == 4)? "当前宠物都是4岁":"当前宠物并不都是4岁");
// noneMatch 全部都不匹配
System.out.println (catList.stream ().noneMatch (a -> a.getAge () == 4) ? "当前没有4岁宠物" : "存在4岁宠物");
聚合求值 reduce()
stream api的reduce方法用于对stream中元素进行聚合求值,最常见的用法就是将stream中一连串的值合成为单个值,比如为一个包含一系列数值的数组求和。
reduce方法有三个重载的方法,方法签名如下
Optional<T> reduce(BinaryOperator<T> accumulator);
T reduce(T identity, BinaryOperator<T> accumulator);
<U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner);
第一个签名方法接受一个 BinaryOperator 类型的 lambada 表达式, 常规应用方法如下:
List<Integer> numList = Arrays.asList(1,2,3,4,5);
int result = numList.stream().reduce((a,b) -> a + b ).get();
System.out.println(result);
代码实现了对 numList 的累加操作,lambda 表达式中的 a 参数是表达式执行结果的缓存,也就是 a 是上次运算的结果,b 作为新值参与运算。
@Test
public void fourteen(){
List<Integer> numList = Arrays.asList(1,2,3,4,5);
int result = numList.stream().reduce((a,b)->{
System.out.println ("a="+a+"\tb="+b);
return a+b;
}).get();
System.out.println("\n累加值="+result);
}
/*
a=1 b=2
a=3 b=3
a=6 b=4
a=10 b=5
累加值=15
*/
因为首次运算没有缓存值,所以直接将第一个、第二个数据赋值给 a ,b。所以第一个重载方法 reduce()将运算结果作为中间值传递。
第二个重载方法实现
T reduce(T identity, BinaryOperator<T> accumulator);
与第一个重载方法不同,第二个重载方法可以为函数运算设立初始值。
List<Integer> numList = Arrays.asList(1,2,3,4,5);
int result = numList.stream().reduce(10,(a,b)->{
System.out.println ("a="+a+" \tb="+b);
return a+b;
});
System.out.println("累加值="+result);
/*
a=10 b=1
a=11 b=2
a=13 b=3
a=16 b=4
a=20 b=5
累加值=25
*/
这两个方法的实现非常相似,第二种只是增加了初始值参数。因为存在stream为空的情况,所以第一种实现并不直接输出计算的结果。而是将计算结果经过Optional来包装。我们在重载一中,可以通过 get( ) 方法获得Integer类型的结果。而 Integer 类型允许值为 null ,第二种实现因为允许指定初始值,因此即时stream为空,也不会出现返回结果为null的情况,当stream为空,reduce 会直接将初始值返回。
第三种重载方法比较特别,他修复了前两种方法的缺陷。即他们的计算结果必须和stream中的元素类型相同,如上面的代码示例,stream中的类型为int,name计算结果也必须为int,这就导致了灵活性的不足,甚至无法完成某些任务,假如需要对一系列的int值求和,但是求和的结果超出了 int 值的存放范围,必须升级为Long,这时就必须使用第三种重载方法。他不将执行结果与 stream 中元素的类型绑死。
Long reduce = Stream.of (Integer.MAX_VALUE , Integer.MAX_VALUE).reduce (0L , Long :: sum , (a , b) -> null);
当使用第二种重载方法进行运算时,提示类型无法转换错误
使用第三种方法,将 int 原数组转化为ArrayList类型的集合
int[] i={1,1,5,6,8,9,4,3,3};
// 将原数组转化为ArrayList<Strings>类型的集合
Arrays.stream(i).asLongStream ().boxed ().reduce (new ArrayList<String>(),(a,b)->{
a.add ("存入数据:"+b.intValue ());
return a;
},(a,b)->null).forEach (System.out :: println);
需要注意的是,这个 reduce 的签名还包含第三个参数,一个BinaryOperator类型的表达式。在常规情况下我们可以忽略这个参数,敷衍了事的随便指定一个表达式即可,目的是为了通过编译器的检查,因为在常规的 stream 中它并不会被执行到,然而,虽然此表达式形同虚设,可是我们也不是将它设置为null,否则还是会报错。在并行stream中,此表达式会被执行到。有关第三个参数的详细信息点击
操作进阶
Stream流中数据聚合/分组/分区/拼接操作
//最大值
Collectors.maxBy();
//最小值
Collectors.minBy();
//总和
Collectors.summingInt();/Collectors.summingDouble();/Collectors.summingLong();
//平均值
Collectors.averagingInt();/Collectors.averagingDouble();/Collectors.averagingLong();
//总个数
Collectors.counting();
聚合操作
//查找集合中的最大值
Optional<Animal> collectMax = catList.stream ().collect (Collectors.maxBy (new Comparator<Animal> (){
@Override
public int compare (Animal o1 , Animal o2){
if (o1.getAge () > o2.getAge ()){
//比大小前值比后值大则返回非负数
return 0;
}
return - 1;
}
}));
//方法引用方式查找最大值
Optional<Animal> catListMax = catList.stream ().max (Comparator.comparingInt (Animal :: getAge));
//统计年龄总和
Integer collectSum = catList.stream ().collect (Collectors.summingInt (Animal :: getAge));
//计算年龄平均值
Double collectAvg= catList.stream ().collect (Collectors.averagingDouble (Animal :: getAge));
//统计个数
Long collectCount = catList.stream ().collect (Collectors.counting ());
System.out.println ("最大年龄1:" + collectMax);
System.out.println ("最大年龄2:" + catListMax);
System.out.println ("统计年龄总和:" + collectSum);
System.out.println ("计算年龄平均值:" + collectAvg);
System.out.println ("统计个数:" + collectCount);
/*
最大年龄1:Optional[Animal(type=金毛, name=旺财, age=5, color=白色)]
最大年龄2:Optional[Animal(type=金毛, name=旺财, age=5, color=白色)]
统计年龄总和:22
计算年龄平均值:3.142857142857143
统计个数:7
进程已结束,退出代码0
*/
分组操作
当我们使用 Stream 流处理数据后,可以根据某个属性来将数据进行分组。
//接收一个 Function 参数
groupingBy(Function<? super T, ? extends K> classifier)
分组关键在于传入的Function函数的返回值,groupingBy()方法根据内部Function函数的返回值进行分组,以Map形式返回数据
//正常写法,根据返回内容分组
Map<Integer, List<Animal>> collectMap1 = catList.stream ().collect (Collectors.groupingBy (new Function<Animal, Integer> (){
@Override
public Integer apply (Animal animal){
return animal.getAge ();
}
}));
//方法引用简写,根据返回内容分组
Map<Integer, List<Animal>> collectMap2 = catList.stream ().collect (Collectors.groupingBy (Animal :: getAge));
collectMap1.forEach ((a,b)->{
System.out.println ("年龄为"+a+":");
b.forEach (System.out :: println);
});
System.out.println ("==========================================");
collectMap2.forEach ((a,b)->{
System.out.println ("年龄为"+a+":");
b.forEach (System.out :: println);
});
复合分组
// 复合分组
// 先根据种类划分,然后根据年龄分组
Map<String, Map<Integer, List<Animal>>> collectMap = catList.stream ().collect (Collectors.groupingBy (Animal :: getName , Collectors.groupingBy (Animal :: getAge)));
collectMap.forEach ((key,value)->{
System.out.println ("分类名称:"+key);
value.forEach ((key1,value1)->{
System.out.println ("\t年龄:"+key1);
value1.forEach (a-> System.out.println ("\t\t"+a));
});
});
/*
分类名称:豆豆
年龄:3
Animal(type=二哈, name=豆豆, age=3, color=黑色)
Animal(type=哈士奇, name=豆豆, age=3, color=黑色)
分类名称:旺财
年龄:5
Animal(type=金毛, name=旺财, age=5, color=白色)
分类名称:来福
年龄:1
Animal(type=泰迪, name=来福, age=1, color=黄色)
分类名称:多多
年龄:2
Animal(type=阿拉斯加, name=多多, age=2, color=白色)
年龄:4
Animal(type=阿拉斯加, name=多多, age=4, color=白色)
Animal(type=阿拉斯加, name=多多, age=4, color=黑色)
分类名称:平安
年龄:2
Animal(type=拉布拉多, name=平安, age=2, color=黑白花色)
*/
分区操作
分区操作,通过使用 Collectors.partitioningBy() ,根据返回值是否为 true,把集合分为两个列表,一个 true 列表,一个 false 列表。
分组和分区的区别就在:分组可以有多个组。分区只会有两个区( true 和 false)
Map<Boolean, List<Animal>> collect = catList.stream ().collect (Collectors.partitioningBy (s -> s.getAge () % 2 == 0));
collect.forEach ((key,value)->{
System.out.println (key);
System.out.println (value);
});
拼接操作
Collectors.joining() 会根据指定的连接符,将所有元素连接成一个字符串。
//无参数--等价于 joining("");
joining()
//一个参数
joining(CharSequence delimiter)
//三个参数(连接符+前缀+后缀)
joining(CharSequence delimiter, CharSequence prefix,CharSequence suffix)
@Test
void test7() {
List<People> peopleList = new ArrayList<>();
peopleList.add(new People(1, "小王", 1));
peopleList.add(new People(2, "小李", 2));
peopleList.add(new People(3, "小张", 2));
peopleList.add(new People(4, "小皇", 1));
String collect1 = peopleList.stream().map(People::getName).collect(Collectors.joining());
System.out.println(collect1);
String collect2 = peopleList.stream().map(People::getName).collect(Collectors.joining(","));
System.out.println(collect2);
String collect3 = peopleList.stream().map(People::getName).collect(Collectors.joining("_", "开始<", ">结束"));
System.out.println(collect3);
}