学Stream,看这篇文章就够了

一、Stream

  • public interface Stream<T> extends BaseStream<T, Stream<T>>

支持顺序和并行聚合操作的元素序列

可能是数组、集合、生成器函数、I/O通道等

流是惰性的;只有在启动终端操作时才对源数据执行计算,并且仅根据需要使用源元素。


1、与I/O流不同,它更像具有Iterable的集合类,但行为和集合类又有所不同

2、Stream是对集合功能的增强,它专注于对集合中的对象进行非常便利、高效的聚合操作,或者大量数据操作

3、只要给出需要对其包含的元素执行什么操作,Stream 会隐式地在内部进行遍历,做出相应的数据转换。

4、 Java中的Stream并不会存储元素,而是按需计算。

  • 并行数据处理

在Java 7之前,处理并行数据集合非常麻烦,首先需要将一个庞大数据集合分成几个子集合;然后需要为每一个子集合编写多线程处理程序,还需要对他们做线程同步来避免访问共享变量导致处理结果不准确;最后,等待所有线程处理完毕后将处理结果合并。在Java 7之后新添加了一个fork/join的框架,让这一切变得更加简单。

创建流

1. Stream类

1.1 of(T)
  • 创建流
public static<T> Stream<T> of(T t) {
        return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
    }
1.2 of(T…)
  • 创建流
public static<T> Stream<T> of(T... values) {
        return Arrays.stream(values);
    }
1.3 empty()
  • 返回一个空的顺序Stream,该Stream里面不包含元素项。
public static<T> Stream<T> empty() {
        return StreamSupport.stream(Spliterators.<T>emptySpliterator(), false);
    }
1.4 concat
  • 将两个Stream合并成一个Stream
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
        Objects.requireNonNull(a);
        Objects.requireNonNull(b);

        @SuppressWarnings("unchecked")
        Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
                (Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
        Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
        return stream.onClose(Streams.composedClose(a, b));
    }
1.5 generate
  • 返回无限流
public static<T> Stream<T> generate(Supplier<T> s) {
        Objects.requireNonNull(s);
        return StreamSupport.stream(
                new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
    }

案例

//通过生成器产生5个10以内的随机数,如果不使用limit就会无限生成10以内随机数
Stream.generate(() -> Math.random() * 10).limit(5).forEach(System.out::println);
----------输出--------
0.8320556195819129
6.260534125204207
7.344094646332503
0.18490598959698068
6.392272744710005
    
generate()方法用于生成一些随机数,比如生成10个UUID
Stream.generate(() -> UUID.randomUUID().toString()).limit(10).forEach(System.out::println);
1.6 iterate
  • 返回无限流,用于生成一系列值
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
        Objects.requireNonNull(f);
        final Iterator<T> iterator = new Iterator<T>() {
            @SuppressWarnings("unchecked")
            T t = (T) Streams.NONE;

            @Override
            public boolean hasNext() {
                return true;
            }

            @Override
            public T next() {
                return t = (t == Streams.NONE) ? seed : f.apply(t);
            }
        };
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
                iterator,
                Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
    }

案例:

打印100以内的所有偶数
Stream.iterate(0, n -> n + 2).limit(51).forEach(System.out::println);

一般来说,iterate()用于生成一系列值,比如生成以当前时间开始之后的10天的日期
Stream.iterate(LocalDate.now(), date -> date.plusDays(1)).limit(10).forEach(System.out::println);

2. Collection类

  • 顺序流

    • new ArrayList<String>().stream();
  • 并行流

    • new ArrayList<String>().parallelStream();
    • Java内部会将流的内容分割成若干个子部分,然后将它们交给多个线程并行处理,这样就将工作的负担交给多核CPU的其他内核处理。

    并行流案例:

    接受一个数字n作为参数,返回从1到n的所有自然数之和
    public static long sequentialSum(long n) {
        return Stream.iterate(1L, i -> i + 1)
                .limit(n)
                .reduce(0L, Long::sum);
    }
    
    

    iterate()方法不适合用并行流处理 ,生成的对象是基本类型的包装类(也就是java.lang.Long类型),必须进行拆箱操作才能运算。会导致效率很低

{
    public static long parallelSum1(long n) {
        return Stream.iterate(1L, i -> i + 1)
                .limit(n)
                .reduce(0L, Long::sum);
    }

    public static long parallelSum2(long n) {
        return Stream.iterate(1L, i -> i + 1)
                .limit(n)
                .parallel()
                .reduce(0L, Long::sum);
    }

    public static long parallelSum3(long n) {
        return LongStream.rangeClosed(1, n)
                .parallel()
                .reduce(0L, Long::sum);
    }

    public static void main(String[] args) {
        long start1 = System.currentTimeMillis();
        long number = 10000000L;
//        parallelSum1(number);//200+
//        parallelSum2(number);//2000+----》并行处理,但是存在自动装箱拆箱的问题,所以很慢
        parallelSum3(number);//75+
        long stop1 = System.currentTimeMillis();
        System.out.println("Parallel Sum: " + (stop1 - start1) + " 毫秒");
    }
}

3. Arrays

  • Arrays.Stream()也可以获取一个数组流

中间操作

1. filter

Stream<T> filter(Predicate<? super T> predicate);

  • 从流中排除某些元素

案例

List<Integer> list = Arrays.asList(1,2,3,523,21,55);
list.stream().filter(x -> x > 10).forEach(System.out::println);

2. distinct

Stream<T> distinct();

  • 去重
 List<Integer> list = Arrays.asList(1,2,3,3,2,4);
  Stream<Integer> stream3 = list.stream().distinct();
  stream3.forEach(System.out::println);

3.sorted

  • 排序
3.1sorted()

Stream<T> sorted();

  • 自然排序 按照Comparable的方式

案例

List<String> list = Arrays.asList("aa","cc","bb");
Stream<String> stream3 = list.stream().sorted();
stream3.forEach(System.out::println);
3.2sorted(Comparator)

Stream<T> sorted(Comparator<? super T> comparator);

  • 定制排序

案例

List<Integer> list = Arrays.asList(1,3,2,6,8,3,9);
Stream<Integer> stream3 = list.stream().sorted(Integer::compare);
stream3.forEach(System.out::println);

4. peek

Stream<T> peek(Consumer<? super T> action);

  • 生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数
  • peek接收一个没有返回值的λ表达式,可以做一些输出,外部处理等

注意:仅在对流内元素进行操作时,peek才会被调用,当不对元素做任何操作时,peek自然也不会被调用了

该方法主要用于调试,方便debug查看Stream内进行处理的每个元素

5. limit

Stream<T> limit(long maxSize);

  • 截断流,使其元素不超过给定数量

案例

List<Integer> list = Arrays.asList(1,2,3,523,21,55);
Stream<Integer> stream3 = list.stream().limit(3);
stream3.forEach(System.out::println);

6. skip

Stream<T> skip(long n);

  • 跳过元素返回一个抛弃了前n个元素的流,若流中元素不满足n个,则返回一个空流,与limit形成互补

案例:

List<Integer> list = Arrays.asList(1,2,3,523,21,55);
Stream<Integer> stream3 = list.stream().skip(3);
stream3.forEach(System.out::println);

7. map

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

  • 应用到每个元素上,并将其映射成一个新的元素。

案例

//map()里面使用函数型接口(Function)
  List<String> list = Arrays.asList("aa","bb","cc");
  Stream<String> stream3 = list.stream().map(String::toUpperCase);
  stream3.forEach(System.out::println);
System.out.println(list);
  ----------------------输出-----------------------
  AA
  BB
  CC
  [aa, bb, cc]
  ------------------------------------------------
  集合里的每一个元素都会使用到String.toUpperCase()方法
  它是以aa作为一个元素,bb作为一个元素 
获取索引
public static int checkIdNumber(String number) {
        Optional<Integer> first = arrayList.stream().map(x -> {
            if (x.getIdNumber().equalsIgnoreCase(number)) {
                return arrayList.indexOf(x);
            }
            return -1;
        }).filter(x -> x != -1).findFirst();
        if (first.isPresent()) {
            return first.get();
        }
        return -1;
    }

8. flatMap

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

  • 将流中的每个值都换成另一个流,然后把所有流连接一个流

flatMap将原来的流转换为一个新的流并且,是以每一个值为单位的

案例:

List<String> list = Arrays.asList("aa","bb","cc");
        Stream<String> stream3 = list.stream().flatMap(l -> {
            String[] strings = l.split("");
            return Arrays.stream(strings);
        });
 stream3.forEach(System.out::println);
 -------------------输出-----------
 a
 a
 b
 b
 c
 c

将多个Stream连接成一个Stream,这时候不是用新值取代Stream的值,与map有所区别,这是重新生成一个Stream对象取而代之。

9. 不常用

  • mapToInt
  • mapToIong
  • mapToDouble
  • flatMapToInt
  • flatMapToIong
  • flatMapToDouble

终止操作

1. forEach

void forEach(Consumer<? super T> action);

  • 迭代流中的每个数据
Stream<String> stream=Stream.of("wa1","wa2");
	stream.forEach(System.out::println);

2. forEachOrdered

如果流有顺序,则用这个,同forEach一样

3. toArray

Object[] toArray();

  • 将流转换为数组
  • Integer[] integers = stream.toArray(Integer[]::new);

4.* reduce

三个

  1. T reduce(T identity, BinaryOperator<T> accumulator);
  2. Optional<T> reduce(BinaryOperator<T> accumulator);
  3. <U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner);

将流中的元素进行合并,形成一个新的值

常见的归约操作包括求和,求最大值或最小值

归约操作一般使用reduce()方法,与map()方法搭配使用,可以处理一些很复杂的归约操作。

案例:

Stream<String> stream = Stream.of("w","a");
System.out.println(stream.reduce((x,y)->x+y).get());
// 获取流
List<Book> books = Arrays.asList(
       new Book("Java编程思想", "Bruce Eckel", "机械工业出版社", 108.00D),
       new Book("Java 8实战", "Mario Fusco", "人民邮电出版社", 79.00D),
       new Book("MongoDB权威指南(第2版)", "Kristina Chodorow", "人民邮电出版社", 69.00D)
);

// 计算所有图书的总价
Optional<Double> totalPrice = books.stream()
       .map(Book::getPrice)
       .reduce((n, m) -> n + m);

5.* collect

2个

  1. <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner);
  2. <R, A> R collect(Collector<? super T, A, R> collector);

对流的汇总操作

  • 生成一个新的list、set、hashSet
Stream.collect(Collectors.toList());
Stream.collect(Collectors.toSet());
Stream.collect(Collectors.toCollection(HashSet::new));
  • 元素总数

.collect(Collectors.counting());

  • 获取平均值

.collect(Collectors.averagingDouble(User::getSalary));

  • 获取总和

.collect(Collectors.summingDouble(User::getSalary));

  • 获取最大值或最小值
.collect(Collectors.maxBy(Comparator.comparingDouble(User::getSalary))); .collect(Collectors.minBy(Comparator.comparingDouble(User::getSalary))); 

  • 分组

.collect(Collectors.groupingBy(User::getSalary));

  • 连接

.collect(Collectors.joining("--"));

  • 分区
Map<Boolean, List<User>> collect1 = user.stream().collect(Collectors.partitioningBy(e -> e.getSalary() > 3000));
System.out.println(collect1);
-----------------输出--------------   
{false=[User{name='张三', age=12, salary=1000.0}], true=[User{name='李四', age=32, salary=4000.0}, User{name='王五', age=40, salary=4000.0}, User{name='王五', age=40, salary=4000.0}]}
  • 多级分组
Map<Double, Map<String, List<User>>> collect = user.stream().collect(Collectors.groupingBy(User::getSalary, Collectors.groupingBy(
                u -> {
                    if ( u.getAge() <= 12) {
                        return "青年";
                    } else if ( u.getAge() <= 32) {
                        return "中年";
                    } else {
                        return "老年";
                    }
                }
        )));
System.out.println(collect);    
-----------------输出--------------    
{4000.0={老年=[User{name='王五', age=40, salary=4000.0}, User{name='王五', age=40, salary=4000.0}], 中年=[User{name='李四', age=32, salary=4000.0}]}, 1000.0={青年=[User{name='张三', age=12, salary=1000.0}]}}

6. min

Optional<T> min(Comparator<? super T> comparator);

求最小值

7. max

Optional<T> max(Comparator<? super T> comparator);

求最大值

8. count

long count();

求总和

9. anyMatch

boolean anyMatch(Predicate<? super T> predicate);

检查流中的任意元素是否满足条件,满足就true

检查是否有匹配至少一个元素

10. allMatch

boolean allMatch(Predicate<? super T> predicate);

检查流中的所有元素是否满足条件,满足就true,一个不满足就false

检查是否所有元素都满足要求

11. noneMatch

boolean noneMatch(Predicate<? super T> predicate);

检查流中的所有元素是否满足条件,满足就false,一个不满足就true

检查是否没有匹配的元素

12. findFirst

Optional<T> findFirst();

查找第一个元素

13. findAny

Optional<T> findAny();

查找任意元素

实际上测试结果发现,findFirst()findAny()返回的都是第一个元素,那么两者之间到底有什么区别?通过查看javadoc描述,大致意思是findAny()是为了提高并行操作时的性能,如果没有特别需要,还是建议使用findAny()方法。

常见的转换

{
        List<Integer> list = Arrays.asList(1, 3, 4, 2, 14, 52, 45, 2);
        System.out.println(list);
        Stream<Integer> stream = list.stream();
        //转换为Object的数组
//        Object[] objects = stream.toArray();
            //转化为Integer的数组
//        Integer[] integers = stream.toArray(Integer[]::new);
        //转化为List
//        List<Integer> collect = stream.distinct().collect(Collectors.toList());
//        System.out.println(collect);
        //转化为hashset
//        HashSet<Integer> collect1 = stream.collect(Collectors.toCollection(HashSet::new));
//        System.out.println(collect1);
//        stream.collect(Collectors.toCollection(ArrayList::new));

        //转化为String
    //  stream.map(String::valueOf).collect(Collectors.joining()).toString();

   		//转化为String[]
        String[] strings = stream.map(s -> s + "asd").toArray(String[]::new);
        System.out.println(strings.toString());
        stream.close();
//永远不会改变原始数据
        System.out.println(list);

    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值