本文内容

  • 引入
  • 测试数据
  • collect(toList())
  • map
  • filter
  • flatMap
  • max 和 min
  • reduce
  • 整合操作
  • 参考资料

Java 8 对核心类库的改进主要包括集合类的 API 和新引入的流(Stream)。流使得程序员得以站在更高的抽象层次上对集合进行操作。

本文主要介绍 java.util.stream 中 Lambdas 表达式的使用。

下载 Demo

引入


假设有个艺术家的列表集合,后面会给出定义(艺术家包含名字,歌曲,国籍等属性),在此先借用一下。若计算来自 UK 的艺术家的人数,如下代码所示:

int count = 0;

for (Artist artist : allArtists) {

    if (artist.isFrom("UK")) {

        count++;

    }

}

很“简单”、很“标准”的写法,无非是遍历一遍,如果是来自 UK 的,计数就增 1。

这段代码是命令式的编程,包含太多样板代码,但样板代码模糊了代码的本意,无法流畅地表达程序员的意图。总体来看,for 循环会将行为和方法混为一谈。个人认为,行为比方法抽象层次要高,一个行为可以由多个方法构成,也就是说,一个行为可能会经由多个方法来完成。

for 循环其实是一个封装了迭代的语法糖。如下代码所示:

int count = 0;

Iterator<Artist> iterator = allArtists.iterator();

while (iterator.hasNext()) {

    Artist artist = iterator.next();

    if (artist.isFrom("UK")) {

        count++;

    }

}

无论如何,上面的方式都不能达到抽象的目的,无法流程地表达意图,而且,本质上都是一种串行化的操作。

下面用 Java 8 新特性 Stream 的方式实现上面功能,如下代码所示:

long count = allArtists.stream().filter(artist -> artist.isFrom("UK")).count();

能够清晰地表达意图;如果想并行,可以在 Stream 上调用 Parallel 方法,再执行后续操作。

整个过程被分解为两个更简单的操作:过滤和计数,看似化简为繁,多了一步,好像是执行了两次循环,而事实上,类库设计更精妙,只需对艺术家集合迭代一次。

这也是函数式编程的思想,假如给定一个名称列表,有的只有一个字符。现要求用逗号做分割符,并返回列表中的名称,字符串中不包含单字母名称,每个名称的首字母都大写。代码很容易写,关键在于用什么思想。这里本质上执行了三个任务:筛选,列表以消除单字符,将列表中每个名称的首字母变换 为大写,然后将列表转化 为一个字符串。在命令式语言中,不得不为三个任务都使用同一低级机制(对列表进行迭代)。函数式语言将筛选、变换和转化视为常见操作,因此它们提供给您从不同视角解决问题的方式。

Scala 分别为筛选、变换和转化概念使用了行业通用的名称,即 filter、map 和 reduce。你会下接下来的 Java 8 中看到类似的类库。

测试数据


Track、Album、Artist 类的具体定义,查看 Demo。你可以想象,假设你有一张 CD,Album 是专辑,就是这张 CD;一个 Album 包含多个音乐,Track 就是每个音乐,其包含音乐名和时长;Artist 就是张 CD 的艺术家,他可能是个人,也可能是团队。

package com.example.java8lambdas.data;

 

import java.util.Arrays;

import java.util.List;

import java.util.stream.Stream;

 

import com.example.java8lambdas.base.Album;

import com.example.java8lambdas.base.Artist;

import com.example.java8lambdas.base.Track;

 

import static java.util.Arrays.asList;

 

public class SampleData {

 

    public static final Artist johnColtrane = new Artist("John Coltrane", "US");

    public static final Artist johnLennon = new Artist("John Lennon", "UK");

    public static final Artist paulMcCartney = new Artist("Paul McCartney", "UK");

    public static final Artist georgeHarrison = new Artist("George Harrison", "UK");

    public static final Artist ringoStarr = new Artist("Ringo Starr", "UK");

 

    public static final List<Artist> membersOfTheBeatles = Arrays.asList(johnLennon, paulMcCartney, georgeHarrison,

            ringoStarr);

    public static final Artist theBeatles = new Artist("The Beatles", membersOfTheBeatles, "UK");

 

    public static final Album aLoveSupreme = new Album("A Love Supreme",

            asList(new Track("Acknowledgement", 467), new Track("Resolution", 442)), asList(johnColtrane));

 

    public static final Album sampleShortAlbum = new Album("sample Short Album", asList(new Track("short track", 30)),

            asList(johnColtrane));

 

    public static final Album manyTrackAlbum = new Album(

            "sample Short Album", asList(new Track("short track", 30), new Track("short track 2", 30),

                    new Track("short track 3", 30), new Track("short track 4", 30), new Track("short track 5", 30)),

            asList(johnColtrane));

 

    public static Stream<Album> albums = Stream.of(aLoveSupreme);

 

    public static Stream<Artist> threeArtists() {

        return Stream.of(johnColtrane, johnLennon, theBeatles);

    }

 

    public static List<Artist> getThreeArtists() {

        return Arrays.asList(johnColtrane, johnLennon, theBeatles);

    }

 

    public static List<Album> getAlbum() {

        return Arrays.asList(aLoveSupreme, sampleShortAlbum, manyTrackAlbum);

    }

}

collect(toList())


由Stream 里的值生成一个列表,这是一个及早求值操作。很多 Stream 操作都是惰性求值,因此,调用 Stream 上的一系列方法后,还需要最后再调用一个类似 collect 的及早求值方法。

所谓,惰性求值方法,最终不产生新集合的方法,Stream 大部分方法都是惰性求值;而像 count 这样最终会从 Stream 产生值的方法是及早求值方法。判断一个操作是惰性求值还是及早求值,只需看它的返回值。如果返回值是 Stream,那么就是惰性求值;否则,就是及早求值。

Stream.of(johnColtrane, johnLennon, theBeatles).collect(toList())

把三个艺术家变成一个 List<Artist> 集合。

map


将一种类型的值转换成另一种类型,也就是,将一个流中的值转换成一个新的流。若有一个 List<Artist> 列表 artists,则

List<String> collects = artists.stream().map(artist -> artist.getName()).collect(toList());

返回所有艺术家的名字。注意,返回的是字符串列表。艺术家包含众多属性,但最后我需要的只是他们的名字。

Java 8 引入了方法引用的概念,因此,上面的代码也可以写成,“类名::方法”的形式:

artists.stream().map(Artist::getName).collect(toList());

map,映射,也可以完成数据类型转换,如下面代码,将字符串转换成大写;将字符串转换成整型;将十六进制转换成十进制:

List<String> strArr = Stream.of("a", "b", "c").map(s -> s.toUpperCase()).collect(toList());

Assert.assertEquals(Arrays.asList("A", "B", "C"), strArr);

 

List<Integer> iArr = Stream.of("1", "2", "3").map(s -> Integer.valueOf(s)).collect(toList());

Assert.assertEquals(Arrays.asList(1, 2, 3), iArr);

 

List<Integer> iArr2 = Stream.of("0A", "0B", "0C").map(s -> Integer.valueOf(s, 16)).collect(toList());

Assert.assertEquals(Arrays.asList(10, 11, 12), iArr2);

filter


遍历并检查元素。保留 Stream 中的一些元素,而过滤掉其他的。若有一个 List<Artist> 列表 artists,则

List<Artist> collect = artists.stream().filter(artist -> "UK".equals(artist.getNationality())).collect(toList());

返回所有国籍为 UK 的艺术家列表。

flatMap


可用 Stream 替换值,将多个 Stream 连接成一个 Stream。若有两个艺术家 List<Artist> 列表 artists1 和 artists2,则

List<Artist> collect = Stream.of(artists1, artists2).flatMap(numbers -> numbers.stream()).collect(toList());

返回两个艺术家列表的集合。

max 和 min


求最大值和最小值。若有三首音乐,则

List<Track> tracks = Arrays.asList(new Track("Bakai", 524), new Track("Violets for Your Furs", 378),

        new Track("Time Was", 541));

Track shortestTrack = tracks.stream().min(Comparator.comparing(track -> track.getLength())).get();

返回时长最小的音乐。

reduce


归约,可以实现从一组值中生成一个值。Stream 的 count、min、max 方法,事实上,都是 reduce 操作。如下所示,累加:

int sum = Stream.of(1, 2, 3).reduce(0, (acc, element) -> acc + element);

整合操作


下面再看另一个例子。假如,找出某张专辑上所有乐队的国籍。可将这个问题分解为如下步骤:

  1. 找出专辑上的所有表演者;
  2. 分辨哪些表演者是乐队;
  3. 找出每个乐队的国籍;
  4. 将找到的国籍放入一个集合。
Set<String> collect = album.getMusicians().filter(artist -> artist.getName().startsWith("The"))

        .map(artist -> artist.getNationality()).collect(toSet());

说明:

  • 艺术家列表既有个人,也有乐队,可以认为“The”开头的是乐队。
  • Album 类的 getMusicians 方法返回的是 Stream 对象。这让我们看到,暴露 Stream 集合也许要比暴露 List 或 Set 对象更好,其最大优点在于,很好地封装了内部实现的数据结构。用户在实际操作中无论如何使用,对不会影响到内部的 List 或 Set。

参考资料


 

下载 Demo

Java 8 新特性——Lambdas 表达式的更多相关文章

  1. Java 8 新特性 - Lambda表达式

    Lambda表达式 vs 匿名类既然lambda表达式即将正式取代Java代码中的匿名内部类,那么有必要对二者做一个比较分析.一个关键的不同点就是关键字 this.匿名类的 this 关键字指向匿名类 ...

  2. Java 8新特性--Lambda表达式作为返回值

    lambda表达式作为方法的返回值:

  3. Java 8 新特性--Lambda表达式作为方法参数

    Lambda表达式的使用场景: 当方法的参数是一个函数式接口时,可以使用Lambda表达式进行简化—— 首先,前提是Runnable接口是一个函数式接口,经过查看源码得知,确实如此: 将Runnabl ...

  4. Java 12 新特性介绍,快来补一补

    Java 12 早在 2019 年 3 月 19 日发布,它不是一个长久支持(LTS)版本.在这之前我们已经介绍过其他版本的新特性,如果需要可以点击下面的链接进行阅读. Java 11 新特性介绍 J ...

  5. Java 8 新特性终极版

    声明:本文翻译自Java 8 Features Tutorial – The ULTIMATE Guide,翻译过程中发现并发编程网已经有同学翻译过了:Java 8 特性 – 终极手册,我还是坚持自己 ...

  6. 【整理】Java 8新特性总结

    闲语: 相比于今年三月份才发布的Java 10 ,发布已久的Java 8 已经算是老版本了(传闻Java 11将于9月25日发布....).然而很多报道表明:Java 9 和JJava10不是 LTS ...

  7. Spring 4支持的Java 8新特性一览

    有众多新特性和函数库的Java 8发布之后,Spring 4.x已经支持其中的大部分.有些Java 8的新特性对Spring无影响,可以直接使用,但另有些新特性需要Spring的支持.本文将带您浏览S ...

  8. Java 8新特性前瞻

    快端午小长假了,要上线的项目差不多完结了,终于有时间可以坐下来写篇博客了. 这是篇对我看到的java 8新特性的一些总结,也是自己学习过程的总结. 几乎可以说java 8是目前为止,自2004年jav ...

  9. Java 8新特性探究(八)精简的JRE详解

    http://www.importnew.com/14926.html     首页 所有文章 资讯 Web 架构 基础技术 书籍 教程 Java小组 工具资源 - 导航条 - 首页 所有文章 资讯 ...

随机推荐

  1. Laravel Blade 模板 @section/endsection 与 @section/show, @yield 的区别

    base layout 中需要使用 @section("section_name") 区块链是什么? @show 继承的 blade 中需要使用 @section("se ...

  2. C/S权限系统得到拼音和五笔的自定义函数(二)

    得到五笔: CREATE FUNCTION [dbo].[fun_getWB](@Str VARCHAR(2000)) RETURNS VARCHAR(2000) AS BEGIN DECLARE @ ...

  3. hdu 1257 一共要多少套拦截系统 (LIS)

    给出导弹的高度 拦截的导弹会比上一次低 至少要几套拦截系统才能防御所有导弹 求一套系统能防御的最大导弹数: 反向LIS求一共要多少套:正向LIS Sample Input8 389 207 155 3 ...

  4. hdu 1181 以b开头m结尾的咒语 (DFS)

    咒语是以a开头b结尾的一个单词,那么它的作用就恰好是使A物体变成B物体现在要将一个B(ball)变成一个M(Mouse),比如 "big-got-them". Sample Inp ...

  5. Linux环境下Node.js的安装配置

    1.   官网下载Node.js 2.   安装Node.js 根据下载内容的不同,提供三种安装方法,选择自己喜欢的方式 2.1.   绿色免安装版(Linux(.tar.gz)) 解压Node-XX ...

  6. 【BZOJ1135】[POI2009]Lyz

    题解: hall定理..第一次听说 思考了半小时无果 二分图匹配时间显然太大 但是有这个hall定理 二分图有完美匹配的充要条件是 对于左边任意一个集合(大小为|s|),其连边点构成的集合(大小为|s ...

  7. Codeforces 460D Little Victor and Set(看题解)

    Little Victor and Set 其他都很好求, 只有k == 3的时候很难受.. 我们找到第一个不大于l的 t, 答案为 l, 3 * t, (3 * t) ^ l 感觉好像是对的, 感觉 ...

  8. Ubuntu 之 atom 安装以及 常用配置

    安装方式如下: 打开终端,使用以下命令安装: sudo add-apt-repository ppa:webupd8team/atom sudo apt-get update sudo apt-get ...

  9. cookie、sesion

    关于保存问题 如果高并发不多的话可以保存session 否则用cookie吧,session可以保存到其他服务器哦,比如其他服务器的redis memacache(没有持久化,崩了登录信息就全没了) ...

  10. 洛谷 P1057 传球游戏 【dp】(经典)

    题目链接:https://www.luogu.org/problemnew/show/P1057 题目描述 上体育课的时候,小蛮的老师经常带着同学们一起做游戏.这次,老师带着同学们一起做传球游戏. 游 ...