Java8-流-使用流

筛选和切片 映射 查找和匹配 规约 数值流 构建流

欢迎访问本人博客:http://wangnan.tech

筛选和切片

用谓词筛选

filter方法
会接收一个谓词(一个返回Boolean)作为参数,并返回一个包括所有符号谓词的元素的流

例子:筛选所有的素菜

List<Dish> vegetarianMenu = menu.stream().filter(Dish::isVegetarian).collect(toList());

筛选各异的元素

distinct方法
他会返回一个元素各异的流,实现原理是根据元素的hashCode和equals方法

例子:筛选偶数,且不重复

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

截断流

limit(n)方法
该方法会返回一个不超过给定长度的流,如果流是有序的,则最多会返回前n个元素

例子:选出热量超过300卡路里的头三道菜

List<Dish> dishes = menu.stream()
                        .filter(d-d.getCalories()>300)
                        .limit(3)
                        .collect(toList());

跳过元素

skip(n)方法,返回一个扔掉前n个元素的流,如果流中元素不足n个,则返回一个空流,请注意limit(n)和skip(n)是互补的

映射

一个常见的数据处理套路就是从某些对象中选择信息,比如在sql里面,可以从表中选择一列

对流中每个元素应用函数

map方法
它会接收一个函数作为参数,这个函数会被应用到每个元素上,并将其映射成一个新的元素(注意是创建一个新的版本,而不是去修改)

例子:提取菜肴的名称

List<String> dishNames = menu.stream().map(Dish::getName).collect(toList());

流的扁平化

例子:对应一张单词表,如果返回一个列表,列出里面各不相同的字符
比如单词列表["Hello","Woeld"]你想要返回的列表["H","e","l","o","W","r","d"]

你可能会觉得很容易,调用distinct方法就可以了

words.stream()
    .map(word->word.split(""))
    .distinct()
    .collect(toList());

这个方法的问题在于,传递给map方法的lambda为每个单词返回了一个String[],因此map返回的流实际上是Stream<String[]>类型,我们真正想要的是Stream<String>

幸好有flatMap来解决这个问题

尝试1:使用map和Arrays.stream()
首先,你需要一个字符流,而不是数组流,有一个Arrays.stream()的方法,可以接受一个数组并产生一个流

words.stream()
    .map(word->word.split(""))
    .map(Arrays::stream)
    .distinct()
    .collect(toList());

这个方案仍然搞不定!因为现在得到的是一个流的列表,你先是把每个单词转换成一个字母数组,然后把每个数组变成一个独立的流。

尝试2:使用flatMap

words.stream()
    .map(word->word.split(""))
    .flatMap(Arrays::stream)
    .distinct()
    .collect(toList());

flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容,所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流,

一言以蔽之,flatMap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流

查找和匹配

检查谓词是否至少匹配一个元素

anyMatch方法
可以回答“流中是否有一个元素能匹配给定的谓词”
例子:菜单里面是否有素食可选择

if(menu.stream().anyMatch(Dish::isVegetarian)){
    ...
}

检查谓词是否匹配所有元素

allMatch()用法同上
与allMatch()相对的是noneMatch()

anyMatch allMatch noneMatch 三个操作都用到了我们所谓的短路,就是大家熟悉的java中的&&和||运算符短路在流中的版本

查找元素

findAny方法
将返回当前流中的任意元素

findFirst
找到第一个元素

规约

reduce操作表达更复杂的查询,比如"计算菜单中的总卡路里"或“菜单中卡路里最高的菜是哪一个” 这需要将流中的元素反复结合起来,得到一个值,比如Integer,这样的查询被归类为规约操作,用函数式编程术语来说,这称为折叠(fold)

求和

int producr = numbers.stream().reduce(1,(a,b)->a*b);

reduce操作是如何作用于一个流的:
lambda反复结合每个元素,知道流被规约为一个值

可以使用最更简洁的代码:

int producr = numbers.stream().reduce(0,Integer::sum);

reduce还有一个重载的变体,它不接受初始值,返回一个Optional对象:
Optional<Integer> sum = numbers.stream().reduce((a,b)->(a+b));

最大值和最小值

Optional<Integer> max = numbers.stream().reduce(Integer::max)

Optional<Integer> min = numbers.stream().reduce(Integer::min)

总结下目前说到的操作

数值流

之前我们看到了可以使用reduce方法计算流中元素的总和,例如:

int calories = menu.stream()
                    .map(Dish::getCalories)
                    .reduce(0,Integer::sum);

这段代码的问题是,它有一个暗含的装箱成本,每个Integer都必须拆箱成一个原始类型,再进行求和,要是可以直接像下面这样调用sum方法,岂不是更好

int calories = menu.stream()
                    .map(Dish::getCalories)
                    .sum();

这是不可能的,问题在于map方法会生成一个Straem<T>,虽然流中的元素是Integer类型,但Streams接口没有定义sum方法,不要担心,Stream API还提供了原始类型流特化

原始类型流特化

Java9引入了三个原始类型特化接口来解决这个问题:IntStream,DoubleStream和LongStream,分别将流中的元素特化为int,long,double,从而避免了暗含的装箱成本,每个接口都带了进行常用数值规约的新方法,比如对数值流求和的sum,找到最大元素的max,此外有必要时再把他们转换回对象流的方法

1.映射到数值流
例子:

int calories = menu.stream()
                    .mapToInt(Dish::getCalories)
                    .sum();

2.转换回对象流
把原始流转换成一般流,可以使用boxed方法

IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();

3.默认值optionalInt
求和有默认值0,但是如果计算intStream中最大的元素,就得换个法子了,因为0是错误的结果,我们知道Optional类,这是一个可以表示值存在或不存在的容器,Optional可以用Integer、String等参考类型来参数化,对于三种原始流特化,也分别有一个optional原始类的特化版本:OptionalInt,OptionalDouble,OptionalLong
例如:

OptionalInt maxCalories = menu.stream().mapToInt(Dish::getCalories).max();

数值范围

java8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成这种范围:range和rangeClosed。第一个参数接受起始值,第二个参数接受结束值。
例子:

InStream evenNumbers = IntStream.rangClosed(1,100).filter(n->n%2==0)

构建流

本节介绍如何从值序、数组、文件来创建流,甚至由生成函数来创建无限流

由值创建流

可以使用静态方法Stream.of,它可以接受任意数量的参数
例如:以下代码创建一个字符串流,然后你可以将字符串转换为大写,再一个个打印出来

Stream<String> stream = Stream.of("Java 8","Lambda","In","Action");
stream.map(String::toUpperCase).forEach(System.out::println);

由数组创建流

可以使用静态方法Arrays.stream从数组创建一个流,例子:

int[] numbers = {2,3,5,7,11,13};
int sum = Arrays.stream(number).sum();

由文件生成流

Files.lines方法,它会返回一个由指定文件中的各行构成的字符串流

创建无限流

Stream API提供两个静态方法来从函数生成流:Stream.iterate和Stream.generate
这两个操作可以创建所谓的无限流:他们产生的流会用给定的函数按需创建值,因此可以无穷无尽地计算下去,一般来说,应该来说应该使用limit(n)来对这种流加以限制,以避免打印无穷多个值

例子

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

generate不是依次对每个生成的值应用函数的,它接受一个Supplier<T>类型的lambda提供新的值

例子

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

小结

  • 流可以简洁地表达复杂的数据处理查询,流可以透明的并行化
  • 你可以使用filter、distinct、skip和limit对流做筛选和切片
  • 你可以使用map和flatMap提取或装换流中的元素
  • 你可以使用findFirst和findAny方法查找流中的元素,你可以使用allMatch、noneMatch和anyMatch方法让流匹配给定的谓词
  • 这些方法都利用了短路:找到结果就立即停止计算,没有必要处理整个流
  • 你可以利用reduce方法将流中的所有元素迭代合并成一个结果,例如求和或查找最大元素
  • filter和map等操作是无状态的,他们并不储存任何状态,reduce等操作要储存状态才能计算出一个值,sorted和distinct等操作也要储存状态,因为他们需要把流中的所有元素缓存起来才能返回一个新的流,这种操作称为有状态操作
  • 流不仅可以从集合创建,也可以从、数组、文件以及iterate与generate等特定方法创建
  • 无限流是没有固定大小的流

(注:内容整理自《Java8实战》)

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

推荐阅读更多精彩内容