Steam流学习笔记,化繁为简

556 阅读11分钟

常用的steam创建方式

由集合创建

Java8 中的 Collection 接口经扩展,提供了两个获取流的default方法

  1. default Stream stream():返回一个顺序流
  2. 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() 可以获取数组流:

  1. static Stream stream(T[] array): 返回一个流
  2. 重载形式,能够处理对应基本类型的数组:
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(), 通过显示值 创建一个流。它可以接收任意数量的参数。

  1. public static Stream of(T... values) : 返回一个流。
 Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8);

由函数创建(创建无限流)

可以使用静态方法 Stream.iterate() 和 Stream.generate()创建无限流。

  1. 迭代 public static Stream iterate(final T seed, final UnaryOperator f)
  2. 生成 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的操作分为两大类,一类为中间操作,一类为终端操作

中间操作:返回值仍然为一个流,不会消耗流

终端操作:返回最终结果;终端操作会消耗掉流,使之不再可用

准备工作

  1. 创建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;
}
  1. 创建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);
    }

参考连接

blog.csdn.net/qq_21918021…

blog.csdn.net/muranchenhu…

blog.csdn.net/weixin_4614…

blog.csdn.net/qq_41378597…

www.cnblogs.com/lyn8100/p/1…

juejin.cn/post/704639…