【Stream流】Java8 Stream 详解

相信Java8的Stream 大家都已听说过了,但是可能大家不会用或者用的不熟,文章将带大家从零开始使用,循序渐进,带你走向Stream的巅峰。

操作符

什么是操作符呢?操作符就是对数据进行的一种处理工作,一道加工程序;就好像工厂的工人对流水线上的产品进行一道加工程序一样。

Stream的操作符大体上分为两种:中间操作符和终止操作符

中间操作符

对于数据流来说,中间操作符在执行制定处理程序后,数据流依然可以传递给下一级的操作符。

中间操作符包含8种(排除了parallel,sequential,这两个操作并不涉及到对数据流的加工操作):

  • map(mapToInt,mapToLong,mapToDouble)
    • 转换操作符,把比如A->B,这里默认提供了转int,long,double的操作符。
  • flatmap(flatmapToInt,flatmapToLong,flatmapToDouble)
    • 拍平操作比如把 int[]{2,3,4} 拍平 变成 2,3,4 也就是从原来的一个数据变成了3个数据,这里默认提供了拍平成int,long,double的操作符。
  • limit
    • 限流操作,比如数据流中有10个 我只要出前3个就可以使用。
  • distint
    • 去重操作,对重复元素去重,底层使用了equals方法。
  • filter
    • 过滤操作,把不想要的数据过滤。
  • peek
    • 挑出操作,如果想对数据进行某些操作,如:读取、编辑修改等。
  • skip
    • 跳过操作,跳过某些元素。
  • sorted(unordered)
    • 排序操作,对元素排序,前提是实现Comparable接口,当然也可以自定义比较器。

终止操作符

数据经过中间加工操作,就轮到终止操作符上场了;终止操作符就是用来对数据进行收集或者消费的,数据到了终止操作这里就不会向下流动了,终止操作符只能使用一次。

  • collect

    • 收集操作,将所有数据收集起来,这个操作非常重要,官方的提供的Collectors 提供了非常多收集器,可以说Stream 的核心在于Collectors。
  • count

    • 统计操作,统计最终的数据个数。
  • findFirst、findAny

    • 查找操作,查找第一个、查找任何一个 返回的类型为Optional。
  • noneMatch、allMatch、anyMatch

    • 匹配操作,数据流中是否存在符合条件的元素 返回值为bool 值。
  • min、max

    • 最值操作,需要自定义比较器,返回数据流中最大最小的值。
  • reduce

    • 规约操作,将整个数据流的值规约为一个值,count、min、max底层就是使用reduce。
  • forEach、forEachOrdered

    • 遍历操作,这里就是对最终的数据进行消费了。
  • toArray

    • 数组操作,将数据流的元素转换成数组。

代码演示

Stream 的一系列操作必须要使用终止操作,否者整个数据流是不会流动起来的,即处理操作不会执行。

map

可以看到 map 操作符要求输入一个Function的函数是接口实例,功能是将T类型转换成R类型的。

    /**
     * Returns a stream consisting of the results of applying the given
     * function to the elements of this stream.
     *
     * <p>This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * @param <R> The element type of the new stream
     * @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *               <a href="package-summary.html#Statelessness">stateless</a>
     *               function to apply to each element
     * @return the new stream
     */
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);

map操作将原来的单词转换成了每个单词的长度,利用了String自身的length()方法,该方法返回类型为int。这里我直接使用了lambda表达式,关于lambda表达式。

public class Main {  
  
    public static void main(String[] args) {  
        Stream.of("apple","banana","orange","waltermaleon","grape")  
                .map(e->e.length()) //转成单词的长度 int  
                .forEach(e->System.out.println(e)); //输出  
    }  
}

当然也可以这样,这里使用了成员函数引用,为了便于理解,后续的例子中将使用lambda表达式而非函数引用。

public class Main {  
  
    public static void main(String[] args) {  
         Stream.of("apple","banana","orange","waltermaleon","grape")  
                .map(String::length) //转成单词的长度 int  
                .forEach(System.out::println);  
    }  
}

运行结果:

5
6
6
12
5

mapToInt

将数据流中得元素转成Int,这限定了转换的类型Int,最终产生的流为IntStream,及结果只能转化成int。

    /**
     * Returns an {@code IntStream} consisting of the results of applying the
     * given function to the elements of this stream.
     *
     * <p>This is an <a href="package-summary.html#StreamOps">
     *     intermediate operation</a>.
     *
     * @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *               <a href="package-summary.html#Statelessness">stateless</a>
     *               function to apply to each element
     * @return the new stream
     */
    IntStream mapToInt(ToIntFunction<? super T> mapper);
public class Main {  
  
    public static void main(String[] args) {  
         Stream.of("apple", "banana", "orange", "waltermaleon", "grape")  
                .mapToInt(e -> e.length()) //转成int  
                .forEach(e -> System.out.println(e));  
    }  
}

运行结果:

5
6
6
12
5

mapToLong、mapToDouble 与mapToInt 类似,只是类型不一样

flatmap

作用就是将元素拍平拍扁,将拍扁的元素重新组成Stream,并将这些Stream 串行合并成一条Stream

    /**
     * Returns a stream consisting of the results of replacing each element of
     * this stream with the contents of a mapped stream produced by applying
     * the provided mapping function to each element.  Each mapped stream is
     * {@link java.util.stream.BaseStream#close() closed} after its contents
     * have been placed into this stream.  (If a mapped stream is {@code null}
     * an empty stream is used, instead.)
     *
     * <p>This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * @apiNote
     * The {@code flatMap()} operation has the effect of applying a one-to-many
     * transformation to the elements of the stream, and then flattening the
     * resulting elements into a new stream.
     *
     * <p><b>Examples.</b>
     *
     * <p>If {@code orders} is a stream of purchase orders, and each purchase
     * order contains a collection of line items, then the following produces a
     * stream containing all the line items in all the orders:
     * <pre>{@code
     *     orders.flatMap(order -> order.getLineItems().stream())...
     * }</pre>
     *
     * <p>If {@code path} is the path to a file, then the following produces a
     * stream of the {@code words} contained in that file:
     * <pre>{@code
     *     Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8);
     *     Stream<String> words = lines.flatMap(line -> Stream.of(line.split(" +")));
     * }</pre>
     * The {@code mapper} function passed to {@code flatMap} splits a line,
     * using a simple regular expression, into an array of words, and then
     * creates a stream of words from that array.
     *
     * @param <R> The element type of the new stream
     * @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *               <a href="package-summary.html#Statelessness">stateless</a>
     *               function to apply to each element which produces a stream
     *               of new values
     * @return the new stream
     */
    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
public class Main {  
    public static void main(String[] args) {  
        Stream.of("a-b-c-d","e-f-i-g-h")  
                .flatMap(e->Stream.of(e.split("-")))  
                .forEach(e->System.out.println(e));  
  
    }  
}

运行结果:

a
b
c
d
e
f
i
g
h

flatmapToInt、flatmapToLong、flatmapToDouble 跟flatMap 都类似的,只是类型被限定了。

limit

限制元素的个数,只需传入 long 类型 表示限制的最大数

public class Main {  
    public static void main(String[] args) {  
        Stream.of(1,2,3,4,5,6)  
                .limit(3) //限制三个  
                //将输出 前三个 1,2,3  
                .forEach(e->System.out.println(e)); 
    }  
}

运行结果:

1
2
3

distinct

去除重复数据

public class Main {  
  
    public static void main(String[] args) {  
  
        Stream.of(1,2,3,1,2,4,3,2,1,0,0,1,2,3,1)  
                .distinct() //去重  
                .forEach(e->System.out.println(e));  
  
    }  
}

运行结果:

1
2
3
0

filter

对某些元素进行过滤,不符合筛选条件的将无法进入流的下游
public class Main {

public static void main(String[] args) {  
    Stream.of(1,2,3,1,2,5,6,7,8,0,0,1,2,3,1)  
            .filter(e->e>=5) //过滤小于5的  
            .forEach(e->System.out.println(e));  
}  

}

运行结果:

5
6
7
8

peek

挑选 ,将元素挑选出来,可以理解为提前消费

public class Main {  
    public static void main(String[] args) {

        User w = new User("w",10);
        User x = new User("x",11);
        User y = new User("y",12);

        Stream.of(w,x,y)
                .peek(e->{e.setName(e.getAge()+e.getName());}) //重新设置名字 变成 年龄+名字
                .forEach(e->System.out.println(e.toString()));

    }

    @Data
    static class User {

        private String name;

        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
}

运行结果:

demo.User(name=10w, age=10)
demo.User(name=11x, age=11)
demo.User(name=12y, age=12)

skip

跳过 元素

public class Main {  
    public static void main(String[] args) {  
        Stream.of(1,2,3,4,5,6,7,8,9)  
                .skip(4) //跳过前四个  
                .forEach(e->System.out.println(e)); //输出的结果应该只有5,6,7,8,9  
    }  
}

运行结果:

5
6
7
8
9

sorted

排序 底层依赖Comparable 实现,也可以提供自定义比较器

这里Integer 实现了比较器

public class Main {  
  
    public static void main(String[] args) {  
        Stream.of(2,6,4,6,0)  
                .sorted()  
                .forEach(e->System.out.println(e));  
    }  
}

运行结果:

0
2
4
6
6

这里使用自定义比较,当然User 可以实现Comparable 接口

public class Main {  
    public static void main(String[] args) {

        User x = new User("x",11);
        User y = new User("y",12);
        User w = new User("w",10);

        Stream.of(w,x,y)
                .sorted((e1,e2)-> Integer.compare(e1.age, e2.age))
                .forEach(e->System.out.println(e.toString()));
    }

    @Data
    static class User {

        private String name;

        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
}

运行结果:

demo.User(name=w, age=10)
demo.User(name=x, age=11)
demo.User(name=y, age=12)

collect

收集,使用系统提供的收集器可以将最终的数据流收集到List,Set,Map等容器中。

这里我使用collect 将元素收集到一个set中

public class Main {  
  
    public static void main(String[] args) {  
        Stream.of("apple", "banana", "orange", "waltermaleon", "grape")  
                .collect(Collectors.toSet()) //set 容器  
                .forEach(e -> System.out.println(e));  
    }  
}

咦?,不是说终止操作符只能使用一次吗,为什么这里调用了forEach 呢?forEach不仅仅是是 Stream 中得操作符还是各种集合中得一个语法糖

public class Main {  
  
    public static void main(String[] args) {  
  
        Set<String> stringSet = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")  
                .collect(Collectors.toSet()); //收集的结果就是set  
        stringSet.forEach(e->System.out.println(e)); set的语法糖forEach  
}

运行结果:

banana
orange
apple
waltermaleon
grape

count

统计数据流中的元素个数,返回的是long 类型

public class Main {  
  
    public static void main(String[] args) {  
  
        long count = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")  
                .count();  
  
        System.out.println(count);  
    }  
}

运行结果:

5

findFirst

获取流中的第一个元素

这里找到第一个元素 apple

public class FindFirst {  
  
    public static void main(String[] args) {  
        Optional<String> stringOptional = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")  
                .findFirst();  
        stringOptional.ifPresent(e->System.out.println(e));  
    }  
}

运行结果:

apple

findAny 获取流中任意一个元素

public class FindAny {  
  
    public static void main(String[] args) {  
        Optional<String> stringOptional = Stream.of("apple", "banana", "orange", "waltermaleon", "grape")  
                .parallel()  
                .findAny(); //在并行流下每次返回的结果可能一样也可能不一样  
        stringOptional.ifPresent(e->System.out.println(e));  
    }  
}

noneMatch

数据流中得没有一个元素与条件匹配的

这里的作用是是判断数据流中一个都没有与aa 相等元素 ,但是流中存在 aa ,所以最终结果应该是false

public class NoneMatch {  
  
    public static void main(String[] args) {  
        boolean result = Stream.of("aa","bb","cc","aa")  
                .noneMatch(e->e.equals("aa"));  
        System.out.println(result);  
    }  
}

运行结果:

false

allMatch和anyMatch 一个是全匹配,一个是任意匹配 和noneMatch 类似。

min

最小的一个,传入比较器,也可能没有(如果数据流为空)

    public static void main(String[] args) {  
  
        Optional<Integer> integerOptional = Stream.of(0,9,8,4,5,6,-1)  
                .min((e1,e2)->e1.compareTo(e2));  
  
        integerOptional.ifPresent(e->System.out.println(e));  
  
    }

运行结果:

-1

max

元素中最大的,需要传入比较器,也可能没有(流为Empty时)

    public static void main(String[] args) {  
  
        Optional<Integer> integerOptional = Stream.of(0,9,8,4,5,6,-1)  
                .max((e1,e2)->e1.compareTo(e2));  
  
        integerOptional.ifPresent(e->System.out.println(e));  
  
    }

运行结果:

9

reduce

是一个规约操作,所有的元素归约成一个,比如对所有元素求和,乘啊等。

这里实现了一个加法,指定了初始化的值

public class Main {  
    public static void main(String[] args) {  
  
        int sum = Stream.of(0,9,8,4,5,6,-1)  
              .reduce(0,(e1,e2)->e1+e2);  
        System.out.println(sum);  
    }  
}

运行结果:

31

forEach

对每个数据遍历迭代

forEachOrdered

适用用于并行流的情况下进行迭代,能保证迭代的有序性

这里通过并行的方式输出数字

public class ForEachOrdered {  
    public static void main(String[] args) {  
        Stream.of(0,2,6,5,4,9,8,-1)  
            .parallel()  
            .forEachOrdered(e->{  
                System.out.println(Thread.currentThread().getName()+": "+e);});  
    }  
}

运行结果:

ForkJoinPool.commonPool-worker-6: 0
ForkJoinPool.commonPool-worker-11: 2
ForkJoinPool.commonPool-worker-11: 6
ForkJoinPool.commonPool-worker-11: 5
ForkJoinPool.commonPool-worker-11: 4
ForkJoinPool.commonPool-worker-11: 9
ForkJoinPool.commonPool-worker-11: 8
ForkJoinPool.commonPool-worker-11: -1

toArray

转成数组,可以提供自定义数组生成器

public class ToArray {  
    public static void main(String[] args) {  
        Object[] objects=Stream.of(0,2,6,5,4,9,8,-1)  
                .toArray();  
  
        for (int i = 0; i < objects.length; i++) {  
            System.out.println(objects[i]);  
        }  
    }  
}

运行结果:

0
2
6
5
4
9
8
-1

常用stream流操作

取出某个实体类某个属性为集合

List<String> streamList = list.stream().map(DictEntity::getName).collect(Collectors.toList());

取出某个实体类某个属性数组

Long[] ids = list.stream().map(DictEntity::getId).toArray(Long[]::new);

集合去重

list.stream().distinct().collect(Collectors.toList());

求和 int、double、long:

double max = list.stream().mapToDouble(User::getHeight).sum();

求平均

double average = list.stream().collect(Collectors.averagingDouble(Double::doubleValue));
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 8 引入了一种新的抽象概念 Stream),它使得对数据的处理变得更加简便和高效。Stream 是一种来自数据源的元素队列并支持聚合操作。 Stream API 借助于lambda表达式,极大的提高了Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用fork/join并行方式来拆分任务和加速处理过程。 Stream 的特性可以归纳为: - 不是数据结构 - 没有存储元素 - 支持延迟计算 - 支持并行处理 Stream 的操作分为中间操作和终止操作。中间操作会返回一个新的 Stream,我们可以对这个新的 Stream 进行下一步的操作。终止操作则会返回一个最终的结果。 Stream 操作可以分为以下几种: - Intermediate(中间)操作:一个可以后面跟随零个或多个Intermediate操作。其目的主要是打开,做出某种程度的数据映射/过滤,然后返回一个新的,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始的遍历。 - Terminal(终止)操作:一个只能有一个 Terminal 操作,当这个操作执行后,就被使用“光”了,无法再被操作。所以这必定是的最后一个操作。Terminal 操作的执行,才会真正开始的遍历,并且会生成一个结果,或者一个 side effect。 Stream API 提供了大量的方法,可以用来完成各种不同的操作,如过滤、映射、筛选、查找、归约等等。这些方法可以分为以下几类: - 转换操作:map、flatMap、filter、distinct、sorted、peek、limit、skip - 聚合操作:forEach、reduce、collect - 匹配操作:allMatch、anyMatch、noneMatch - 查找操作:findFirst、findAny - 统计操作:count、min、max、average、sum Stream API 的使用可以大大简化代码,增加可读性和可维护性。同时,由于它的并行特性,可以有效地提升程序的性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值