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,映射,也可以完成数据类型转换,如下面代码,将字符串转换成大写;将字符串转换成整型;将十六进制转换成十进制:
新闻热点
疑难解答