最完整全面的java8 Stream流

Stream流

要点介绍:

  1. 原有的迭代器,禁止了高效的并发,所以不如Stream

  2. 可以从 集合、数组、生成器、迭代器中创建Stream流

  3. 可以用 limit、distinct、sorted改变Stream

  4. 可以用reduction操作符,从Stream中获得结果 例如(count()、max()、min()、findFirst()、findAny),可能会返回Option值

  5. Optional的目的是为了安全的替代使用null值,可以借助ifPresent或者orElse方法

  6. 可以获取集合、数组、字符串和map里的Stream结果

  7. Collections类的groupingBy和partitioningBy可以对Stream中内容分组,取得每个组的结果

  8. 每个原始类型都有专门的stream也有专门的函数式接扣

Stream和集合的区别

  1. 对集合进行迭代,很难进行并行运算

  2. Stream看着和集合很类似

    1. Stream不会改变源数据

    2. Stream自己不会存储元素,元素存在于底层集合根据需要产生

    3. Stream可能延迟执行,只在需要结果的时候执行。例如只要五个数,满足结果之后就停止执行了。

    4. Stream 原则是,“做什么,而不是怎么去做

    5. 使用stream操作的步骤

      1. 创建Stream

      2. 转换Stream

      3. 收集结果






1、创建Stream

1、都是依靠StreamSupport创建流

2、对于实现Collection接口的,可以使用新增的默认方法:Collection.stream()

3、对于数组或单个对象,可以使用Stream.of()来创建

4、Stream类的静态方法Stream.iteratoe()、和Stream.generaotr()可以创建无限的Stream

1.1 collection的default stream()方法

对于继承Collection接口的类,都可以使用其新增的方法,由spliterator创建

//创建顺序流
default Stream<E> stream() {
   return StreamSupport.stream(spliterator(), false);
}

//创建一个并行流,允许返回顺序流
default Stream<E> parallelStream() {
  return StreamSupport.stream(spliterator(), true);
}

1.2 使用Stream的Stream.of方法


    //1、创建单个元素的Stream 
    public static<T> Stream<T> of(T t) {
        return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
    }

    //2、根据不定长的参数数组创建Stream
    public static<T> Stream<T> of(T... values) {
        return Arrays.stream(values);
    }






1.2 使用Arrays.stream(T[] array) 方法从数组创建

	//Arrays.Stream(values),该方法含有重载方法,可截取一部分来创建流
	public static <T> Stream<T> stream(T[] array) {
        return stream(array, 0, array.length);
    }
	//stream(array, 0, array.length);
	public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) {
        return StreamSupport.stream(spliterator(array, startInclusive, endExclusive), false);
    }

1.3 生成无限的Stream

/**
 * 1、Stream.generater生成无限长度的Stream,可以生成,有规律的数据,
 * 	前一个生成的值将作为第二个值的参数。序列的第一个数是seed 后续则为f(seed)
 *   public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
 *   UnaryOperator extends Function<T, T> 只是限制了返回值和输入值必须为同一类型。
 *   如下代码生成了从1开始递增1的无限递增序列
 */
Stream<Integer> infiniteStreamIterate=Stream.iterate(1,a->a+1);

/**
 * 2、public static<T> Stream<T> generate(Supplier<T> s)
 * 可以生成任意类型的(随机/重复)无限数据序列
 * 如下代码生成了无限个 1的Stream
 */
Stream<Integer> infiniteStreamGenerate=Stream.generate(()->1);






2、转换Stream,filter、map、flatMap

  1. 转换Stream,是对一个流进行处理或过滤产生一个新的流
  2. filter方法的参数是一个 Predict对象,一个从T到boolean的函数
  3. map方法,传递一个转换的函数

2.1 filter和map转换流和flatmap

filter和map

//filter筛选小于100的流
Stream<Integer> infiniteStreamIterateFilter=infiniteStreamIterate.filter(a->a<100);

//map将Integer转换为String
Stream<String> infiniteStreamGenerateMap=infiniteStreamGenerate.map(a->a.toString());

flatmap水平转换

将一个会返回结果为Stream的map,的结果 水平展开后放到同一个Stream而不是单独的Stream

区别:

  • 直接map收集 {"小刘", "小李", "小王", "小张"} -> [[小、刘],[小、李].....]
  • flatmap水平收集 {"小刘", "小李", "小王", "小张"} -> [小、刘,小、李.....]
//map和flatMap的区别
//将String切割成,Character的Stream
Function<String, Stream<Character>> function = a -> {
    List<Character> characterList = new ArrayList<>();
    for (char c : a.toCharArray()) {
        characterList.add(c);
    }
    return characterList.stream();
};
String[] stringArray = {"小刘", "小李", "小王", "小张"};
Stream<String> nameStream = Arrays.stream(stringArray);
//直接利用map收集结果 {"小刘", "小李", "小王", "小张"} ->[[小、刘],[小、李].....]
Stream<Stream<Character>> nameCharacterStream = nameStream.map(function);
//利用flatMap 水平收集Stream {"小刘", "小李", "小王", "小张"} ->[小、刘,小、李.....]
Stream<Character> charStream = nameStream.flatMap(function);

2.2 提取子流和组合流

  1. Stream.limit(n) 截取前n个元素,如果流中元素少与n则返回全部

  2. Stream.skip(m)丢弃前m个元素

  3. Stream.peek (Function) 产生一个与原来的元素一样的流,但是每次获取元素时都会调用一下一个函数,便于调试

2.3 有状态转换流、去重、排序

  1. 无状态:流中的元素互不相关,单个元素的筛选结果与其他元素无关

  2. **Stream.distinct() 去重 **Stream uniqueWord=Stream.of({"小刘“,“小刘”}).distinct();

  3. **Stream.sort() 排序 **Stream.sorted(Comparator.comparing(String::length).reversed());






3、聚合结果(聚合成一个值)

3.0 基本的聚合方法 findFirst、findAny、anyMatch、allMatch、noneMatch

  1. findFirst 取出流中第一个值,一般与filter一起用 取出流的第一个值

  2. findAny 也是取出流中第一个值 但是在并行流中并不一定是流序列的第一个 而是时间上第一个满足条件的值,这个值满足条件且第一个被取出但不一定是序列的第一个

  3. anyMatch 接收predict参数,判断流中是否有符合条件的元素,对应的还有allMatch和noneMatch 用于判断所有元素都符合,和所有元素都不符合的情况

3.2 聚合操作 stream.reduce

说明:用上一个操作的结果,作为后一个操作的参数,一直到整个流执行完。有点像lambda函数中的函数复合

除了情况1 返回对应的 可选值Optional类型,其他两种情况都直接返回 对应类型的对象。

情况1:

Optional reduce(BinaryOperator accumulator);

//调用按理
nameStream.reduce((s1,s2)->s1+s2);

//源码说明:
//使用关联累积函数对此流的元素执行聚合,并返回描述缩减值的Optional (如果有)。 
//这相当于:
//     boolean foundAny = false;
//     T result = null;
//     for (T element : this stream) {
//         if (!foundAny) {
//             foundAny = true;
//             result = element;
//         }
//         else
//             result = accumulator.apply(result, element);
//     }
//     return foundAny ? Optional.of(result) : Optional.empty();
 

//accumulator函数必须是关联函数,但不限于顺序执行。

//这是一个terminal操作。
//参数:
accumulator 一个关联的、无干扰的、无状态的函数,用于组合两个值
//返回:
描述聚合结果的 Optional
//抛出:
NullPointerException – 如果归约结果为空
//也可以看看:
reduce(Object, BinaryOperator) , min(Comparator) , max(Comparator)

Optional<T> reduce(BinaryOperator<T> accumulator);



情况2

T reduce(T identity, BinaryOperator accumulator);
和情况1类似,但是多了一个T类型的初始值,这样在Stream为空时,也会返回初始值的值。

情况3
对于返回的值类型,和stream类型不一致,例如 <T,T>->M 对于此种情况,需要多提供一个方法,则需要提供两个方法

1、accumulator 联合结果和下一个元素,结果类型为返回类型
2、并行计算时,accumulator 会产生多个结果,需要再提供一个方法,将结果类型 再联合起来

例如


int result=words.reduce
(
	0,
	(total,word)->total+word.length(),
	(total_1,total_2)->total_1+total_2
);


<U> U reduce(U identity,
                 BiFunction<U, ? super T, U> accumulator,
                 BinaryOperator<U> combiner);






4、收集结果

4.0 stream 获取迭代器用来访问元素

  • 生成一个普普通通的迭代器,来取得其中元素
/**
 * Returns an iterator for the elements of this stream.
 *
 * <p>This is a <a href="package-summary.html#StreamOps">terminal
 * operation</a>.
 *
 * @return the element iterator for this stream
 */
Iterator<T> iterator();

4.1 stream 收集成数组

  • 使用Stream.toArray() 方法 收集成 Object[]

  • 使用Stream.toArray(IntFunction<T[] > generator ) 来生成指定类型的Stream结果

//字符串 Stream
Stream<String> nameStream=Stream.of("大刘","小刘","小王","小王2","小李","小张");
//默认生成对象数组
Object[] objectArray=nameStream.toArray();
//生成指定类型的泛型数组,JAVA没办法new泛型的数组
// 所以利用IntFunction  这个函数式接口 R apply(int value); 用来快速创建对应类型的数组
//(r)->new String[r] 也可以换成方法引用 String[] :: new
String[] stringArray=nameStream.toArray((r)->new String[r]);

4.2 collect方法收集

4.2.1 collect方法有三个参数

1. 创建目标对象类型实例的方法(目标对象不一定是集合,例如可以是StringBuilder)

1. 将元素添加到目标元素中的方法

1. 将两个对象整合到一起的方法 例如addAll

4.2.2 Collector接口为常用的收集类型提供了各个工厂方法

1、例如将流收集到List或者Set,使用Collectors.toSet() 或者自定义类型Collectors.toCollection(TreeSet::new)

ArrayList<String> arrayNameList=nameStream.collect(Collectors.toCollection(ArrayList::new));
List<String> nameList=nameStream.collect(Collectors.toList());
Set<String> nameSet=nameStream.collect(Collectors.toSet());
//如果需要返回其他类型,可以使用Collectors.toCollection(构造函数)
TreeSet<String> nameTreeSet=nameStream.collect(Collectors.toCollection(TreeSet::new));

2、将流作为字符串连接并收集 Collectors.joining()

//如果将字符串类型流进行拼接
String allString =nameStream.collect(Collectors.joining(","));
//将对象类型转化为字符串类型后再拼接
allString=nameStream.map(Object::toString).collect(Collectors.joining());

3、计算数值流的总和、平均值、最大值、最小值 summarziong(int|long|Double)

IntSummaryStatistics intSummaryStatistics=nameStream.collect(Collectors.summarizingInt(String::length));
intSummaryStatistics.getSum();
intSummaryStatistics.getMax();
intSummaryStatistics.getCount();
intSummaryStatistics.getMin();
intSummaryStatistics.getAverage();

4、遍历流

1. 使用forEach 但是对于并行流,该方法不能保证遍历的顺序

1. 使用forEachOrdered 可保证遍历顺序

5、将结果收集到Map中Collectors.toMap

Collectors.toMap有四个函数参数

    1. 第一个是生成key

    1. 第二个生成value

    1. 第三个是key一样时对value的处理,**可以实现简单分组,更完整的分组,在下方第五节**

    1. 第四个是对于Map有类型要求时的构造函数
//Collectors.toMap有四个函数参数,第一个是生成key,第二个生成value,第三个是key一样时对value的处理,第四个是对于Map有类型要求时的构造函数
//1、生成不同类型的value值
Map<String,Person> personMap=personStream.collect(Collectors.toMap(Person::getIdNumber,s->s));
personMap=personStream.collect(Collectors.toMap(Person::getIdNumber, Function.identity()));
Map<String,String> personIdNameMap=personStream.collect(Collectors.toMap(Person::getIdNumber,Person::getName));
//2、对于Key重复的值处理-1,如果不处理会抛出异常IllegalStateException,这里保留后面一个
 personMap=personStream.collect(Collectors.toMap(Person::getIdNumber,Function.identity(),(existValue,newValue)->newValue));

//3、对于key重复的值处理-2收集成Set 或者List 作为Value的Map
 (简单分组) Collections.singleton从一个对象生成了一个集合
Map<String,Set<String>> personIdNameList=personStream.collect(Collectors.toMap(Person::getIdNumber, l->Collections.singleton(l.getName()),(existvalue,newvalue)->{
Set<String> s=new HashSet<>(existvalue);
 s.addAll(newvalue);
return s;
}));
//4、对于需要自定义返回Map类型,可以使用第四个参数来
HashMap<String,String> personIDMameMap2=personStream.collect(Collectors.toMap(Person::getIdNumber,Person::getName,(existValue,newValue)->newValue,HashMap::new));



//Collector.toMap 方法
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                Function<? super T, ? extends U> valueMapper,
                                BinaryOperator<U> mergeFunction) {
    return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}






5、分组和分片Collectors.groupingBy()

5.1 Collectors.groupingBy 分组

Collectors.groupingBy()的三个参数,按照参数个数有三个重载方法

Collectors.groupingBy(
参数1:从元素类型中,取出分组的对象 ,
参数2:分组为Map,在此指定创建集成于Map具体容器,例如TreeMap,
参数3: 传入 Collector 对象,对分组结果的处理,相当于对子Stream的一次收集
)

  1. 按照指定的参数将数据分成对应的Map<key(分组字段),<List分组结果>>
  2. personList.stream().collect(Collectors.groupingBy(Person::getGender));
  3. groupingBy 按照布尔值分组时,使用partitioningBy比groupingBy更有效率
  4. Collectors.groupingByConcurrent() 会获得一个并发MAP,当流是并行流时,会并发地插入值。和toConcurrentMap类似
  5. 分组方法,可以接受第二个参数,对分组后的元素组结果进行 downStream处理,例如
  6. 常用downStream 收集器 Collectors.toSet(),Collectors.counting()、Collectors.summarizingInt、Collectors.summingInt()、Collectors.maxBy()Collectors.minBy()
//Collectors.groupingBy(
// 参数1:从元素类型中,取出分组的对象 ,
// 参数2:分组为Map,在此指定创建Map具体容器,例如TreeMap,
// 参数3: 传入 Collector 对象,对分组结果的处理,相当于对子Stream的一次收集)


//1、groupingBy()第一个参数 按照性别分组示例
Map<String, List<Person>> genderGroupListMap = personList.stream().collect(Collectors.groupingBy(p -> p.getGender()));
genderGroupListMap = personList.stream().collect(Collectors.groupingBy(Person::getGender));
//2、groupingBy 按照布尔分组时,使用partitioningBy比groupingBy更有效率
Map<Boolean, List<Person>> malePersonListMap = personList.stream().collect(Collectors.partitioningBy(s -> "男".equals(s.getGender())));
//3、Collectors.groupingByConcurrent() 会获得一个并发MAP,当流是并行流时,会并发地插入值。和toConcurrentMap类似
Map<Boolean, List<Person>> concurrentMap = personList.parallelStream().collect(Collectors.groupingByConcurrent(s -> "男".equals(s.getGender())));
//4、统计、求和、最大值最小值等操作
//4.1统计总条数 counting
Map<String, Long> genderCountMap = personList.stream().collect(Collectors.groupingBy(Person::getGender, Collectors.counting()));
//4.2统计总长度 summingInt
Map<String, Integer> nameLengthMap = personList.stream().collect(Collectors.groupingBy(Person::getGender, Collectors.summingInt(a -> a.getName().length())));
//4.3 求名字最长的 
Map<String, Optional<Person>> maxNameLengthMap = personList.stream().collect(Collectors.groupingBy(Person::getGender, Collectors.maxBy(Comparator.comparing(s->s.name.length())) ));
maxNameLengthMap = personList.stream().collect(Collectors.groupingBy(Person::getGender, Collectors.minBy(Comparator.comparing(s->s.name.length())) ));

5.1 对分组后的元素进行转换 Collectors.mapping()

使用方式:

Collectors.groupingBy
(
生成用于分组的值函数,
mapping(参数1对downStream结果的元素进行处理 参数2 对分组的整个结果进行处理 )
)

示例:

//groupingBy 和Mapping,对分组结果里的元素进行转换再进行downStream操作
Map<String,Optional<String>> genderShortestNameMap=personList.stream().collect(
        Collectors.groupingBy(
                Person::getGender,
                Collectors.mapping(
                        Person::getName,
                        Collectors.minBy(
                                Comparator.comparing(s->s.length())
                        )
                )
        )
);

**5.2 收集成Set结果 downStream =Collectors.toSet() **

//收集成Set
Map<String,Set<Person>> genderNameSetMap=personList.stream().collect(Collectors.groupingBy(Person::getGender,Collectors.toSet()));

5.3 分组结果的summary statistics 聚合分析结果 ,当分组后的元素可以转换为 long|int|double的时候可以使用

//6、对于double int long 类型,可以收集成分析后的一些聚合分析结果,结果包含 最大值、最小值、平均数、总数、总和
TreeMap<String,IntSummaryStatistics> genderAgeStatistics=personList.stream().collect(
       Collectors.groupingBy(
                Person::getGender,TreeMap::new,Collectors.summarizingInt(Person::getAge)
      )
);

5.4 downStream方法的 聚合方法,reducing

三种形式,和3.2类似

  1. reducing(binaryOperator)
  2. reducing(identity,binaryOperator)
  3. reducing(identity,mapper,binaryOperator)
    一般用 3.2 的Stream.reducing即可,很少用到Collectors.reducing()

5.5 总结downSteam收集器

downSteam收集器可以产生非常复杂的表达式,一般只在通过groupingBy或者 partitioningBy产生了 downSteam时,才使用他们。一般情况下只需要对流直接使用 map、reduce、count、max、min即可






6、原始类型流

类似于常见的Lambada函数,Stream流也有类似的原始类型流。避免了对基本类型流处理时重复包装造成效率低下。

对于原始类型,Stream Api 提供了,IntStream、LongStream、DoubleStream 原始流类型

  1. 如果需要存储,short、char、byte、boolean、类型的值,直接使用IntStream

  2. 如果需要存储float直接使用DoubleStream 类型即可。






7、并行流

流 使得并行计算变得容易,他的处理过程几乎是自动的。

使用:要使用并行流,需要生成一个并行流,默认情况创建Stream默认创建的是串行流,可以使用 1、Collections.paralleStream创建并行流 2、parallel方法,可以将串行流转换为并行流

说明:

  1. 正确的并行流操作,应当返回和串行流一致的操作结果。并且这些操作是无状态的。

  2. 需要保证传递给并行流执行的方法都是线程安全的

  3. 默认情况下,从有序集合、范围值、生成器、迭代器 或者Stream.sorted产生的流都是有序的,不考虑顺序时,一些操作可以更有效的运行。使用Stream.unordered方法可以不关心顺序,例如Stream.distinct就是可以从中获益。

  4. map合并的开销很大,所以Collectors.groupingByConcurrent使用了共享的并发Map,将流标记为并行模式能更提高执行效率。

posted @ 2021-09-12 20:32  青花石  阅读(703)  评论(0编辑  收藏  举报