本文内容

  • 引入
  • 测试数据
  • 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. python 全栈开发,Day119(Flask初识,Render Redirect HttpResponse,request,模板语言 Jinja2,用户登录例子,内置Session)

    一.Flask初识 首先,要看你学没学过Django 如果学过Django 的同学,请从头看到尾,如果没有学过Django的同学,并且不想学习Django的同学,轻饶过第一部分 三大主流Web框架对比 ...

  2. BBC 记录片planet earth

    He'll have to remain on guard for another two weeks, but in the jungle, just surviving the day can c ...

  3. SpringMVC后台token防重复提交解决方案

    本文介绍如何使用token来防止前端重复提交的问题. 目录 1.思路 2.拦截器源码实现 3.注解源码 4.拦截器的配置 5.使用指南 6.结语 思路 1.添加拦截器,拦截需要防重复提交的请求 2.通 ...

  4. Struts2(接受表单参数)请求数据自动封装和数据类型转换

    Struts2请求数据自动封装: (1)实现原理:参数拦截器 (2)方式1:jsp表单数据填充到action中的属性:        普通的成员变量,必须给set,get可以不给的.    注意点,A ...

  5. java12小时制的时间转换为24小时制

    Java中将12小时制的时间转换为24小时制的方式如下: import java.text.SimpleDateFormat; import java.util.Date; public class ...

  6. mydumper备份原理和使用方法

    mydumper介绍 MySQL自身的mysqldump工具支持单线程工作,依次一个个导出多个表,没有一个并行的机,这就使得它无法迅速的备份数据. mydumper作为一个实用工具,能够良好支持多线程 ...

  7. Codeforces Round #309 (Div. 2) -D. Kyoya and Permutation

    Kyoya and Permutation 这题想了好久才写出来,没看题解写出来的感觉真的好爽啊!!! 题目大意:题意我看了好久才懂,就是给你一个序列,比如[4, 1, 6, 2, 5, 3],第一个 ...

  8. 009 搭建Spark的maven本地windows开发环境以及测试

    在看完下面的细节之后,就会发现,spark的开发,只需要hdfs加上带有scala的IDEA环境即可.  当run运行程序时,很快就可以运行结束. 为了可以看4040界面,需要将程序加上暂定程序,然后 ...

  9. 《Android进阶之光》--网络编程与网络框架

    No1: Volley源码分析: Volley.newRequestQueue-> RequestQueue.start()-> CacheDispatcher.start()->C ...

  10. hdu 1394 (线段树求逆序数)

    <题目链接> 题意描述: 给你一个有0--n-1数字组成的序列,然后进行这样的操作,每次将最前面一个元素放到最后面去会得到一个序列,那么这样就形成了n个序列,那么每个序列都有一个逆序数,找 ...