Java8中的stream

流与集合

使用for循环遍历集合标准的写法:

    int count = 0;
    for(Artist artist: allArtists){
        if(artisst.isFrom("London")){
            count++;
        }
    }

存在的问题:
第一个问题每次需要迭代集合类的的时候,我都要写这样的5行代码或者更多,并且将这样的代码想要改成并行运行的方式也十分的麻烦,需要修改每个for循环才能够实现。

第二个问题就是在于这样的写法本身就是阅读性很差的,这个例子相当简单,你可能几秒钟就看理解了,但是面对一个多层循环嵌套的集合迭代操作,想看明白,那就相当头疼了。

第三个问题在于,for循环从本质上来讲是一种串行化的操作,从总体来看的话,使用for循环会将行为和方法混为一谈。

外部迭代与内部迭代

上文所用到的for循环是来自java5的增强for循环,本质上是属于iterator迭代器的语法糖,这种使用迭代器的迭代集合的方式,称之为外部迭代,说的通俗一点,就是需要我们程序猿手动的对这个集合进行种种操作才能得到想要结果的迭代方式,叫做外部迭代。
  

与外部迭代所对应的,则是内部迭代,内部迭代是集合本身内部通过流进行了处理之后,程序猿们只需要直接取结果就行了,这种迭代称为内部迭代。

具体实现方式如下:

    long count = allArtists.stream()//进行流操作
                           .filter(artist -> artist.isFrom("London"))//选出所有来自伦敦的艺术家
                           .count();//统计他们的数量

每次需要迭代的时候,并不需要写同样的代码块,改成并行操作,只需要将第一行的stream()改为parallelStream()就可以了

第二个好处就 是可阅读性,第一行使用流对集合进行内部操作,第二步筛选出来自伦敦的艺术家,第三步计数。

第三个好处其实第一点已经提到的,轻松的并行化。

常用流的api

1.获取流对象
对于集合来说,直接通过stream()方法即可获取流对象

List<Person> list = new ArrayList<Person>(); 
Stream<Person> stream = list.stream();

对于数组来说,通过Arrays类提供的静态函数stream()获取数组的流对象

String[] names = {"chaimm","peter","john"};
Stream<String> stream = Arrays.stream(names);

直接将几个普通的数值变成流对象

Stream<String> stream = Stream.of("chaimm","peter","john");

2.collect(toList())
collect(Collectors.toList())方法是将stream里的值生成一个列表,也就是将流再转化成为集合,是一个及早求值的操作。

关于惰性求值与及早求值,这里简单说明一下,这两者最重要的区别就在于看操作有没有具体的返回值(或者说是否产生了具体的数值),比如上文的的统计来自英国艺术家人数的代码,第二行代码的操作是首先筛选出来自英国的艺术家,这个操作并没有实际的数值产生,因此这个操作就是惰性求值,而最后的count计数方法,产生了实际的数值,因此是及早求值。惰性求值是用于描述stream流的,因此返回值是stream,而几乎所有对于流的链式操作都是进行各种惰性求值的链式操作,最后加上一个及早求值的方法返回想要的结果

你可以用建造者的设计模式去理解他,建造者模式通过一系列的操作进行设置与配置操作,最后调用一个build方法,创建出相应的对象。对于这里也是同样,调用各种惰性求值的方法,返回一个stream流,最后一步调用一个及早求值的方法,得到最终的结果。

list.stream()//将集合转化成流
    .???()//一系列惰性求值的操作,返回值为stream
    .collect(toList())//及早求值,这个及早求值的方法返回值为集合,再将流转化为集合

3.筛选filter

filter函数接收一个Lambda表达式作为参数,该表达式返回boolean,在执行过程中,流将元素逐一输送给filter,并筛选出执行结果为true的元素。

    long count = allArtists.stream()
                           .filter(artist -> artist.isFrom("London"))//惰性求值筛选
                           .count();//及早求值统计

4.去重distinc

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

这样只增加了一行,便达到了筛选出所有来自英国的艺术家,并且去掉重复的名字之后的统计数量的目的

5.截取limit

截取流的前N个元素

    long count = allArtists.stream()
                           .filter(artist -> artist.isFrom("London"))
                           .limit(N)
                           .count();

6.跳过skip
跳过流的前N个元素:

    long count = allArtists.stream()
                           .filter(artist -> artist.isFrom("London"))
                           .skip(N)
                           .count();

7.映射map

如果有一个函数可以将一种类型的值转换成另外一种类型,map操作就可以使用该函数,将一个流中的值转换成一个新的流。

映射这个操作其实在大家编程的过程中都经常用到,也就是将A映射成B A->B
还是用艺术家的例子,现在要获得一个包含所有来自伦敦艺术家的名字的集合

    List<String> artistNames = allArtists.stream()
                                         .filter(artist -> artist.isFrom("London"))
                                         .map(artist -> artist.getArtistName())//将艺术家集合映射成了包含艺术家名字的集合
                                         .collect(Collects.toList());

请注意,这里的传递的Lambda表达式必须是Function接口的一个实例,Function接口是只包含一个参数的普通函数接口。

8.flatMap
上一条已经介绍过map操作,它可以用一个新的值代替stream里的值,但有时候,用户希望让map操作有点变化,生成一个新的steram对象取而代之,用户通常不希望结果是一连串的流,此时flatMap能够派上用场。

通俗的一点的说法是,他可以将一条一条的小流,汇聚成一条大流,好比海纳百川的感觉。

用一个简单的例子就很容易理解了
假设有一个包含多个集合的流,现在希望得到所有数字的序列,利用flatMap解决办法如下

    List <Integer> together = Stream.of(asList(1,2),asList(3,4))
                                    .flatMap(numbers -> numbers.stream())
                                    .collect(toList());
    together.forEach(n -> System.out.println(n));

输出结果为1,2,3,4

steram流,在java8里,你可以理解成流水线,流水线的上的商品就是集合里一个个的元素,而这些对于流的各种各样的流操作,就是流水线上加工这些商品的机器。所以呢,stream流的相关特性与之也符合

不可逆,无论是河流,水流,还是流水线,没听过有倒流的,因此java8中的流也同样如此,你不可能在操作完第一个元素之后回头再重新操作,这在流操作里是无法完成的。
另一个特性就是内部迭代,这在一开始已经讲述过了。

9.max和min

例子:获得所有艺术家中,年龄最大的艺术家

想一想,采用原始的外部迭代,要达到这么简单的要求是不是忽然感觉很麻烦?排个序?还是写一个交替又或者是选择比较的算法?何必这么麻烦!使用流操作采用内部迭代就好了,这不是我们程序猿应该专门写一段外部程序来解决的问题!

Stream上常用的操作之一是求最大值和最小值,事实上通过流操作来完成最大值最小值的方式有很多很多种,这里介绍的max和min的方法是stream类里的直接附带的方法,事实上在实际操作的时候我并不会选择这种操作方式
使用流操作如下:

    Artist theMaxAgeArtist = allArtists.stream()
                                       .max(Comparator.comparing(artist -> artist.getAge()))
                                       .get();

我们一行一行地说

第一行,转化为流对象,读到这里的你相信已经十分理解了,因此以后对于这一行不再说明了

第二行,查找Stream中最大或最小的元素,首先要考虑的是用什么作为排序的条件,这里显然是根据艺术家的年龄作为指标,为了让Stream对象按照艺术家的年龄进行排序,需要传给它一个Comparator对象,java8提供了一个新的静态方法comparing,使用它可以方便的实现一个比较器。放在以前,我们需要比较两个对象的某个属性的值,现在只需要提供一个get方法就可以了。
这个comparing方法很有意思,这个方法接受一个函数作为参数,并且返回另一个函数。这在其他语言里听起来像是废话,然而在java里可不能这么认为,这种方法早就该引入Java的标准类库,然而之前的实现方式只能是匿名内部类的实现,无论是看起来,还是写起来,都是相当的难受,所以一直就没有实现,但是现在有了Lambda表达式,就变得很简洁啦。

第三行,max()方法返回的是一个Optional对象,这个对象对我们来确实有点陌生,第11条我会专门对这个对象进行介绍,在这里需要记住的是,通过get方法可以取得Optional对象中的值。

10.归约reduce
reduce操作可以实现从一组值中生成一个值。在上述例子中用到的count,min,max方法,因为经常使用,所以被纳入了标准库里,实际上,这些方法都是由reduce操作实现的。

reduce函数接收两个参数:

初始值

进行归约操作的Lambda表达式

举个例子 使用reduce进行求和

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

reduce的第一个参数表示初始值为0;
reduce的第二个参数为需要进行的归约操作,它接收一个拥有两个参数的Lambda表达式,以上代码acc参数代表当前的数值总和,element代表下一个元素,reduce会把流中的元素两两输给Lambda表达式,最后将计算出累加之和。
也就是说每次acc+element的返回值都会赋给acc
在上述求和例子中,计算过程是这样的 初始值为0

0 + 1 = 1
1 + 2 = 3
3 + 3 = 6

以上三行就是 acc + elment = acc ,其中acc的初始值为reduce的第一个参数(在本例中初始值为0)

上面的方法中我们自己定义了Lambda表达式实现求和运算,如果当前流的元素为数值类型,那么可以使用Integer提供了sum函数代替自定义的Lambda表达式,如:

int age = list.stream().reduce(0, Integer::sum);

Integer类还提供了min、max等一系列数值操作,当流中元素为数值类型时可以直接使用。

注: 上面的Integer::sum如果不理解的话,这是java8中引用的方法,是一种简写语法,属于语法糖。
一般格式为类名(或者是类的实例对象) :: 方法名(注意这里只是方法名,没有括号),这里引用了Integer里的sum函数(java8里新增的),下面是Integer里的sum函数源码

    /**
     * Adds two integers together as per the + operator.
     *
     * @param a the first operand
     * @param b the second operand
     * @return the sum of {@code a} and {@code b}
     * @see java.util.function.BinaryOperator
     * @since 1.8
     */
    public static int sum(int a, int b) {
        return a + b;
    }

11.Optional对象
Optional是Java8新加入的一个容器,这个容器只存1个或0个元素,它用于防止出现NullpointException,它提供如下方法:

isPresent()
判断容器中是否有值。

ifPresent(Consume lambda)
容器若不为空则执行括号中的Lambda表达式。

T get()
获取容器中的元素,若容器为空则抛出NoSuchElement异常。

T orElse(T other)
获取容器中的元素,若容器为空则返回括号中的默认值。

值得注意的是,Optional对象不仅可以用于新的Java 8 API,也可用于具体领域类中,和普通的类并没有什么区别,当试图避免空值相关的缺陷,如捕获的异常时,可以考虑一下是否可使用Optional对象。

Java8中的stream

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值