【JAVA8新特性】- 使用流Stream(转载)

Java 8 中的 Stream 俗称为流,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。

Stream 用于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作。Stream API 借助于Lambda 表达式,极大的提高编程效率和程序可读性。
同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势。通过下面的例子我们可以初步体会到使用 Stream 处理集合的便利性。

初探Stream

有如下一个List,现要从中筛选出以J开头的元素,然后转换为大写,最后输出结果。Java 8之前我们是这样做的:

        List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift");

        List<String> filterList = new ArrayList<>();

        for (String str : list) {
            if (str.startsWith("J")) {
                filterList.add(str.toUpperCase());
            }
        }

        for (String str : filterList) {
            System.out.println(str);
        }

为了筛选集合我们进行了两次外部迭代,并且还创建了一个用来临时存放筛选元素的集合对象。借助Java 8中的Stream我们可以极大的简化这个处理过程:

        List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift");
        
        list.stream()
                .filter(s -> s.startsWith("J"))
                .map(String::toUpperCase)
                .forEach(System.out::println);

是不是很方便?上面的例子中,集合使用stream方法创建了一个流,然后使用filter和map方法来处理这个集合,它们统称为中间操作。中间操作都会返回另一个流,以便于将各种对集合的操作连接起来形成一条流水线。最后我们使用了forEach方法迭代筛选结果,这种位于流的末端,对流进行处理并且生成结果的方法称为终端操作

总而言之,流的使用一般包括三件事情:

  1. 一个数据源(如集合)来执行一个查询。
  2. 一个中间操作链,执行流水线,并能生成结果。
  3. 一个终端操作,执行流水线,并能生成结果。

下表列出了流中常见的中间操作和终端操作:

操作 类型 返回类型 使用的类型/函数式接口 函数描述符
filter 中间 Stream<T> Predicate<T> T -> boolean
distinct 中间 Stream<T>
skip 中间 Stream<T> Long
limit 中间 Stream<T> Long
map 中间 Stream<R> Function<T,R> T->R
flatMap 中间 Stream<R> Function<T,Stream<R>> T->Stream<R>
sorted 中间 Stream<T> Comparator<T> (T, T) -> int
anyMatch 终端 boolean Predicate<T> T -> boolean
noneMatch 终端 boolean Predicate<T> T -> boolean
allMatch 终端 boolean Predicate<T> T -> boolean
findAny 终端 Optional<T>
findFirst 终端 Optional<T>
forEach 终端 void Consumer<T> T -> void
collect 终端 R Collector<T, A, R>
reduce 终端 Optional<T> BinaryOperator<T> (T, T) -> T
count 终端 long

中间操作

filter

Streams接口支持·filter方法,该方法接收一个Predicate<T>,函数描述符为T -> boolean,用于对集合进行筛选,返回所有满足的元素:

        List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");

        list.stream()
                .filter(s -> s.contains("#"))
                .forEach(System.out::println);

结果输出 C#

distinct

distinct方法用于排除流中重复的元素,类似于SQL中的distinct操作。比如筛选中集合中所有的偶数,并排除重复的结果:

        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 2, 4, 6, 9, 11);
        numbers.stream()
                .filter(number->number%2==0)
                .distinct()
                .forEach(System.out::println);

结果输出:2 4 6

skip

skip(n)方法用于跳过流中的前n个元素,如果集合元素小于n,则返回空流。比如筛选出以J开头的元素,并排除第一个:

        List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");

        list.stream()
                .filter(s->s.startsWith("J"))
                .skip(1)
                .forEach(System.out::println);

结果输出: JavaScript

limit

limit(n)方法返回一个长度不超过n的流

        List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");

        list.stream()
                .limit(3)
                .forEach(System.out::println);

结果输出:Java JavaScript python

map

map方法接收一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素。

        List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");

        list.stream()
                .limit(3)
                .map(String::length)
                .forEach(System.out::println);

结果输出:4 10 6

map还支持将流特化为指定原始类型的流,如通过mapToInt,mapToDouble和mapToLong方法,可以将流转换为IntStream,DoubleStream和LongStream。特化后的流支持sum,min和max方法来对流中的元素进行计算。比如:

        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);

        IntStream intStream = numbers.stream().mapToInt(a -> a);
        System.out.println(intStream.sum());

结果输出:45

也可以通过下面的方法,将IntStream转换为Stream:

Stream<Integer> integerStream = intStream.boxed();

flatMap

flatMap用于将多个流合并成一个流,俗称流的扁平化。这么说有点抽象,举个例子,比如现在需要将list中的各个元素拆分为一个个字母,并过滤掉重复的结果,你可能会这样做:

        list.stream()
                .map(s -> s.split(""))
                .distinct()
                .forEach(System.out::println);

结果输出:

[Ljava.lang.String;@21b8d17c
[Ljava.lang.String;@6433a2
[Ljava.lang.String;@5910e440
[Ljava.lang.String;@6267c3bb
[Ljava.lang.String;@533ddba
[Ljava.lang.String;@246b179d
[Ljava.lang.String;@7a07c5b4
[Ljava.lang.String;@26a1ab54
[Ljava.lang.String;@3d646c37

这明显不符合我们的预期。实际上在map(s -> s.split(""))操作后,返回了一个Stream<String[]>类型的流,所以输出结果为每个数组对象的句柄,而我们真正想要的结果是Stream<String>!

在Stream中,可以使用Arrays.stream()方法来将数组转换为流,改造上面的方法:

        List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");

        list.stream()
                .map(s -> s.split(""))
                .map(Arrays::stream)
                .distinct()
                .forEach(System.out::println);

结果输出:

java.util.stream.ReferencePipeline$Head@6267c3bb
java.util.stream.ReferencePipeline$Head@533ddba
java.util.stream.ReferencePipeline$Head@246b179d
java.util.stream.ReferencePipeline$Head@7a07c5b4
java.util.stream.ReferencePipeline$Head@26a1ab54
java.util.stream.ReferencePipeline$Head@3d646c37
java.util.stream.ReferencePipeline$Head@41cf53f9
java.util.stream.ReferencePipeline$Head@5a10411
java.util.stream.ReferencePipeline$Head@2ef1e4fa

因为上面的流经过map(Arrays::stream)处理后,将每个数组变成了一个新的流,返回结果为流的数组Stream<String>[],所以输出是各个流的句柄。我们还需将这些新的流连接成一个流,使用flatMap来改写上面的例子:

        List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");

        list.stream()
                .map(s -> s.split(""))
                .flatMap(Arrays::stream)
                .distinct()
                .forEach(s -> System.out.print(s+" "));

结果输出:J a v S c r i p t y h o n P H C # G l g w f + R u b

和map类似,flatMap方法也有相应的原始类型特化方法,如flatMapToInt等。

终端操作

anyMatch

anyMatch方法用于判断流中是否有符合判断条件的元素,返回值为boolean类型。比如判断list中是否含有SQL元素:

        List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");

        System.out.println(
                list.stream().anyMatch(s->"SQL".equals(s))
        );

结果输出:false

allMatch

allMatch方法用于判断流中是否所有元素都满足给定的判断条件,返回值为boolean类型。比如判断list中是否所有元素长度都不大于10:

        List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");

        System.out.println(
                list.stream().allMatch(s->s.length()<=10)
        );

结果输出:true

noneMatch

noneMatch方法用于判断流中是否所有元素都不满足给定的判断条件,返回值为boolean类型。比如判断list中不存在长度大于10的元素:

        List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");

        System.out.println(
                list.stream().noneMatch(s->s.length()>10)
        );

结果输出:true

findAny

findAny方法用于返回流中的任意元素的Optional类型,例如筛选出list中任意一个以J开头的元素,如果存在,则输出它:

        List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");

        list.stream()
                .filter(s -> s.startsWith("J"))
                .findAny()
                .ifPresent(System.out::println);

结果输出:Java

findFirst

findFirst方法用于返回流中的第一个元素的Optional类型,例如筛选出list中长度大于5的元素,如果存在,则输出第一个:

        List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");

        list.stream()
                .filter(s -> s.length()>5)
                .findFirst()
                .ifPresent(System.out::println);  // JavaScript

reduce

reduce函数从字面上来看就是压缩,缩减的意思,它可以用于数字类型的流的求和,求最大值和最小值。如对numbers中的元素求和:

        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
        
        System.out.println(
                numbers.stream()
                        .reduce(0, Integer::sum)   // 45
        );

reduce函数也可以不指定初始值,但这时候将返回一个Optional对象,比如求最大值和最小值:

        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);

        numbers.stream()
                .reduce(Integer::max)
                .ifPresent(System.out::println);  // 9

        numbers.stream()
                .reduce(Integer::min)
                .ifPresent(System.out::println);  // 1

forEach

forEach用于迭代流中的每个元素,最为常见的就是迭代输出,如:

        List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby");

        list.stream().forEach(System.out::println);

count

count方法用于统计流中元素的个数,比如:

        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);

        numbers.stream().count();  // 9

collect

collect方法用于收集流中的元素,并放到不同类型的结果中,比如List、Set或者Map。举个例子:

        List<String> list = Arrays.asList("Java", "JavaScript", "python", "PHP", "C#", "Golang", "Swift", "C++", "Ruby", "JavaScript");

        Set<String> set = list.stream()
                .filter(s -> s.startsWith("J"))
                .collect(Collectors.toSet());

        set.stream().forEach(System.out::println);

结果输出:Java JavaScript

流的构建

除了使用集合对象的stream方法构建流之外,我们可以手动构建一些流。

数值范围构建

IntStream和LongStream对象支持range和rangeClosed方法来构建数值流。这两个方法都是第一个参数接受起始值,第二个参数接受结束值。但range是不包含结束值的,而rangeClosed则包含结束值。比如对1到100的整数求和:

IntStream.rangeClosed(1,100).sum();  // 5050

由值构建

静态方法Stream.of可以显式值创建一个流。它可以接受任意数量的参数。例如,以下代码直接使用Stream.of创建了一个字符串流:

Stream<String> s = Stream.of("Java", "JavaScript", "C++", "Ruby");

也可以使用Stream.empty()构建一个空流:

Stream<Object> emptyStream = Stream.empty();

由数组构建

静态方法Arrays.stream可以通过数组创建一个流。它接受一个数组作为参数。例如:

int[] arr = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(arr);

由文件生成流

java.nio.file.Files中的很多静态方法都会返回一个流。例如Files.lines方法会返回一个由指定文件中的各行构成的字符串流。比如统计一个文件中共有多少个字:

        long wordCout = 0L;
        try (Stream<String> lines = Files.lines(Paths.get("file.txt"), Charset.defaultCharset())) {
            wordCout = lines.map(l -> l.split(""))
                    .flatMap(Arrays::stream)
                    .count();
        } catch (Exception e) {
            e.printStackTrace();
        }

由函数构造

Stream API提供了两个静态方法来从函数生成流:Stream.iterate和Stream.generate。这两个操作可以创建所谓的无限流。比如下面的例子构建了10个偶数:

Stream.iterate(0, n -> n + 2)
      .limit(10).forEach(System.out::println);

iterate方法接受一个初始值(在这里是0),还有一个依次应用在每个产生的新值上的Lambda(UnaryOperator类型)。这里,我们使用Lambda n -> n + 2,返回的是前一个元 素加上2。因此,iterate方法生成了一个所有正偶数的流:流的第一个元素是初始值0。然后加上2来生成新的值2,再加上2来得到新的值4,以此类推。

与iterate方法类似,generate方法也可让你按需生成一个无限流。但generate不是依次对每个新生成的值应用函数,比如下面的例子生成了5个0到1之间的随机双精度数:

Stream.generate(Math::random)
      .limit(5)
      .forEach(System.out::println);

输出结果如下:

0.0067050134787670634
0.6037087300000793
0.8620511990077468
0.709510142584238
0.2984472188425574

参考博文:https://mrbird.cc/java8stream1.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • Jav8中,在核心类库中引入了新的概念,流(Stream)。流使得程序媛们得以站在更高的抽象层次上对集合进行操作。...
    仁昌居士阅读 3,615评论 0 6
  • 为获得更好的阅读体验,请访问原文:传送门 一、流(Stream)简介 流是 Java8 中 API 的新成员,它允...
    我没有三颗心脏阅读 1,393评论 1 2
  • 一、什么是Stream流(WHAT) 在Java中,集合和数组是我们经常会用到的数据结构,需要经常对他们做增、删、...
    GeekerLou阅读 979评论 0 11
  • Java8 in action 没有共享的可变数据,将方法和函数即代码传递给其他方法的能力就是我们平常所说的函数式...
    铁牛很铁阅读 1,187评论 1 2
  • 了解Stream ​ Java8中有两个最为重要的改变,一个是Lambda表达式,另一个就是Stream AP...
    龙历旗阅读 3,291评论 3 4