一起来学Java8(七)——Stream(上)

从Java8开始,新增了一个java.util.stream包,这个包下的类和接口用来处理集合中的元素,在这个包下面有一个Stream接口,我们主要使用这个接口来对集合进行操作。

创建Stream

首先来看下创建Stream有哪几种方式。

使用Stream自带的静态方法生成Stream对象,常见的静态方法有以下几个:

  • Stream.of(T)
  • Stream.of(T… values)
  • Stream.generate(Supplier)
  • Stream.iterate(T, UnaryOperator)
  • Stream.empty()

现在来看下每个静态方法的作用

Stream.of(T) & Stream.of(T… values)

Stream.of是由两个重载方法组成,一个传入单值,一个传入数组

String[] arr = {"hello", "world"};
Stream streamArr = Stream.of(arr);

String str = "hello world";
Stream streamSingle = Stream.of(str);

Stream.generate & Stream.iterate

Stream.generate和Stream.iterate可以用来生成具有多个元素的Stream,如果不加控制会一直生成下去,一般配合limit(n)使用

先来看下Stream.iterate

Stream<Integer> stream5 = Stream.iterate(0,  n-> n+1)
            .limit(5);
stream5.forEach(System.out::println);

打印

0
1
2
3
4

Stream.iterate方法第一参数设定一个初始值,第二个参数表示基于这个初始值,每次循环后返回一个新的值替换这个初始值。limit(5)表示循环5次结束,最后Stream中包含了5个元素。

再来看下Stream.generate

Stream.generate方法只有一个Supplier参数,意思是每次循环执行Supplier接口方法返回一个新的值,放入到Stream中,由于Supplier是一个函数式接口,因此可以直接写成Lambda表达式

AtomicInteger i = new AtomicInteger();
Stream.generate(()-> {
    return i.getAndIncrement();
})
.limit(5)
.forEach(System.out::println);

上面的代码同样打印0~4。

除了Stream静态方法之外,还可以使用Collection接口中的stream()方法来生成Stream对象。

Collection<String> list = Arrays.asList("hello", "world");
Stream streamList = list.stream();

同理,只要是Collection接口的子类或实现类都可以使用stream()方法。

操作Stream

Stream中的方法有很多,大致归纳如下表格所示

方法 方法参数 返回类型 描述
filer Predicate Stream 过滤数据
distinct Stream 去重
map Function<T, R> Stream 返回新的数据
flatMap Function<T, R> Stream<T, Stream> 返回新的数据,并做扁平化处理
sort Comparator Stream 对数据进行排序操作
limit long Stream 截取前几条数据
skip long Stream 跳过几条数据
anyMatch Predicate boolean 匹配任意一条数据,如果匹配到返回true
noneMatch Predicate boolean 如果没有匹配到数据,返回true
allMatch Predicate boolean 如果所有数据全部匹配到,返回true
findAny Optional 返回任意一条数据
findFirst Optional 返回第一条数据
count long 返回元素个数
forEach Consumer void 遍历元素,执行Consumer
collect Collector<T, A, R> R 元素收集
reduce BinaryOperator Optional 数据汇总

从方法的返回结果可以看出,这些方法可以分为两大类,一类是返回Stream对象,可以继续对Stream操作,这类方法也被称之为中间操作(Intermediate operations),另一类是返回非Stream,结束操作,这类方法也被称之为中端操作(Terminal operations),这两类方法往往一起配合操作。

filter

filter方法用来筛选出我们想要的数据,方法参数是一个Predicate接口,因为Predicate是一个函数式接口,我们可以使用Lambda表达式来写。

Integer[] arr = { 1, 2, 3, 4, 5 };
long count = Stream.of(arr)
        .filter(i -> i % 2 == 0)
        .count();
System.out.println("偶数数量:" + count);

在这个例子中,我们筛选出了偶数数字,并且统计出偶数的数量。如果要打印每个偶数,可以使用forEach方法

Stream.of(arr)
        .filter(i -> i % 2 == 0)
        .forEach(System.out::println);

打印:

2
4

如果要查找任意一个元素,可以使用findAny

int num = Stream.of(arr)
        .filter(i -> i % 2 == 0)
        .findAny()
        .orElse(0);
System.out.println("findAny:" + num);

注意,findAny()返回的是一个Optional对象,因为有可能没有找到数据,因此需要开发者自己处理没有找到数据的情况。同理findFirst也是返回一个Optional对象。

distinct

distinct方法会对元素进行去重操作,类似于SQL中的SELECT distinct xx

Stream.of(1,1,2,3,3,4)
    .distinct()
    .forEach(System.out::println)

打印

1
2
3
4

sorted

使用sorted方法可以对元素进行排序操作

Stream.of(6,1,7,2,8,5)
    .sorted()
    .forEach(System.out::println);

打印

1
2
5
6
7
8

sorted()默认是从小到大排列,如果要从大到小降序,可以使用.sorted(Comparator.reverseOrder())

Stream.of(6,1,7,2,8,5)
    .sorted(Comparator.reverseOrder())
    .forEach(System.out::println);

可以看到,sorted方法允许传入一个比较器Comparator让开发者自己实现排序逻辑。下面是一个自定义Comparator例子:

@Data
@AllArgsConstructor
static class Goods {
    private String goodsName;
    private int price;
}

Stream.of(
        new Goods("iphoneX", 4000)
        , new Goods("mate30 pro", 5999)
        , new Goods("redmek20", 2999)
        )
.sorted((goods1, goods2) -> {
    return Integer.compare(goods1.getPrice(), goods2.getPrice());
})
.forEach(System.out::println);

这个列子演示了按商品价格从低到高排序。此处的sorted部分可以简化为:.sorted(Comparator.comparing(Goods::getPrice))

map

map方法可以返回一个新的数据对象,组成一个新的Stream。

List<Goods> list = Arrays.asList(
        new Goods("iphoneX", 4000)
        , new Goods("mate30 pro", 5999)
        , new Goods("redmek20", 2999)
        );
list.stream()
    .map(goods -> goods.getGoodsName())
    .forEach(System.out::println);

上面的示例演示的是从原有的商品对象中拿到商品名称,然后组成一个新的List,其效果等同于

List<String> goodsNameList = new ArrayList<>(list.size());
for(Goods goods : list) {
    goodsNameList.add(goods.getGoodsName());
}

map方法一般配合collect()方法一起使用

List<Goods> list = Arrays.asList(
        new Goods("iphoneX", 4000)
        , new Goods("mate30 pro", 5999)
        , new Goods("redmek20", 2999)
);
List<String> nameList = list.stream()
        .map(goods -> goods.getGoodsName())
        .collect(Collectors.toList());

collect(Collectors.toList())的意思是将Stream中的元素转换成List

flatMap

flatMap()方法是map()方法的扁平化处理,与map不同的是,flatMap把返回Stream对象操作交给开发者自己处理。看下面的例子:

Stream<String[]> stream = Stream.of("I am Java", "hello world")
        .map(s -> s.split(" "));

这个例子的本意是想要将每个字符串进行拆分,把单词单独放入到Stream中,由于map返回的是一个字符串数组String[],因此得到的Stream对象的泛型参数就是Stream<String[]>,而不是Stream<String>。

解决办法是使用flatMap:

Stream<String> stream2 = Stream.of("I am Java", "hello world")
                .flatMap(s -> Stream.of(s.split(" ")));
stream2.forEach(System.out::println);

打印:

I
am
Java
hello
world

综合示例
下面来看一个综合示例,演示的功能是:查询商品名称,价格大于3000,按价格降序

public class StreamTest3 {
    @Data
    @AllArgsConstructor
    static class Goods {
        private String goodsName;
        private int price;
    }

    public static void main(String[] args) {
        List<Goods> list = Arrays.asList(
                new Goods("iphoneX", 4000)
                , new Goods("mate30 pro", 5999)
                , new Goods("redmek20", 2999)
        );
        // 查询商品名称,价格大于3000,按价格降序
        List<String> nameList = list.stream()
                .filter(goods -> goods.getPrice() > 3000)
                .sorted(Comparator.comparing(Goods::getPrice).reversed())
                .map(Goods::getGoodsName)
                .collect(Collectors.toList());
        System.out.println(nameList);
    }
}

打印:[mate30 pro, iphoneX]

代码对应的SQL为:

SELECT goods_name FROM goods WHERE price > 3000 ORDER BY price DESC

小结

本篇讲解了如何创建Stream以及Stream一些常用方法的使用方式,我们将会在下一篇着重讲解collect()和reduce()的用法。

https://shimo.im/docs/gGQHg8xPC3X8cPHV/ 《2020年最新Java架构师系统进阶资料免费领取》,可复制链接后用石墨文档 App 或小程序打开

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

推荐阅读更多精彩内容