java8 新特性(二)之Stream

1、Stream流

得益于Lambda所带来的函数式编程,引入一个全新的Stream概念。用于解决已有集合类库既有的弊端。
集合循环遍历的弊端
集合都支持遍历操作,Java8中Lambda让我们更专注于做什么,而不是怎么做。在之前的循环遍历中,可以发现:

  • for循环的语法就是怎么做
  • for循环的循环体才是做什么

使用循环的目的是遍历,遍历是指每一个每一个元素逐一进行处理,而不是从第一个到最后一个顺次处理的循环,遍历是目的,循环是方式。

试想一下,如果希望对集合中的元素进行筛选过滤:

  1. 将集合A根据条件过滤为子集合B。
  2. 然后将子集合B按照另外的条件过滤为子集合C。
  3. 接下来还有循环遍历去使用。
    每次对元素进行操作的时候只能进行遍历、再遍历,因为线性循环只能遍历一次,这种方式很不爽。

Lambda的衍生物Stream能给我们带来更优雅的写法

public class Demo1StreamFilter{
    public static void main(String[] args){
        List<String> list = new ArrayList<>();
        list.add("张三");
        list.add("李四");
        list.add("张三疯");
        list.stream().filter(s -> s.startsWith("张"))
        .filter(s -> s.length() == 3)
        .forEach(System.out::println);
    }
}

直接阅读代码的字面意思就可以完美展示逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印。这种方式并没有体现使用线性循环或者是其他任何算法进行遍历,我们真正要做的事情内容被更好的体现在代码当中。

1.1 流思想的概述

当需要对多个元素进行操作的时候,考虑到性能及便利性,我们应该首先拼好一个模型不走方案,然后再按照方案去执行它。
Stream是一个来自数据源的元素队列。

元素是特定类型的对象,形成一个队列,java中的Stream并不会存储元素,而是按需计算。 数据源是流的来源,可以是集合,数组等。
整体来看流式思想类似于工厂车间的 生产流水线

当需要对多个元素进行操作(特别是多步操作)的时候,考虑到性能及便利性,我们应该首先拼好一个 模型 步骤方案,然后再按照方案去执行她。
在这里插入图片描述

在这里插入图片描述
这张图中展示了过滤、映射、跳过、计数等操作,这是一种集合元素的处理方案,就是一种函数模型,图中每一方框都是一个流,调用指定的方法,可以从一个流模型转换为另一个流模型,最右侧的数值3就是最终结果。
这里的filter、map、skip都是对函数模型进行操作,集合元素并没有真正被处理,只有在终结方法count执行的时候,整个模型才会按照指定的策略进行操作,这得益于Lambda的延时执行特性。
流其实是一个元素的函数模型,它并不是集合、也不是数据结构,其本身并不存储元素。

1.2 stream内部迭代和外部迭代

一般来说,我们之前的编码方法,叫外部迭代,stream的写法叫内部迭代。内部迭代代码更加可读更加优雅,关注点是做什么(外部迭代关注是怎么样做),也很容易让我们养成编程小函数的好习惯!这点在编程习惯里面非常重要!看例子:

import java.util.stream.IntStream;

public class StreamDemo1 {

  public static void main(String[] args) {
    int[] nums = { 1, 2, 3 };
    // 外部迭代
    int sum = 0;
    for (int i : nums) {
      sum += i;
    }
    System.out.println("结果为:" + sum);

    // 使用stream的内部迭代
    // map就是中间操作(返回stream的操作)
    // sum就是终止操作
    int sum2 = IntStream.of(nums).map(StreamDemo1::doubleNum).sum();
    System.out.println("结果为:" + sum2);

    System.out.println("惰性求值就是终止没有调用的情况下,中间操作不会执行");
    IntStream.of(nums).map(StreamDemo1::doubleNum);
  }

  public static int doubleNum(int i) {
    System.out.println("执行了乘以2");
    return i * 2;
  }
}

1.3 stream操作类型

当使用一个流的时候,通常包括三个步骤:获取一个数据源—>数据转换—>执行操作获取想要的结果,每次转换原有的Stream对象不改变,返回新的Stream对象(可以多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。
操作类型概念要理清楚。有几个维度。

首先分为 中间操作最终操作,在最终操作没有调用的情况下,所有的中级操作都不会执行。那么那些是中间操作那些是最终操作呢?
简单来说,返回stream流的就是中间操作,可以继续链式调用下去,不是返回stream的就是最终操作。这点很好理解。

最终操作里面分为短路操作非短路操作,短路操作就是limit/findxxx/xxxMatch这种,就是找了符合条件的就终止,其他的就是非短路操作。在无限流里面需要调用短路操作,否则像炫迈口香糖一样根本停不下来!

中间操作又分为 有状态操作无状态操作,怎么样区分呢?
一开始很多同学需要死记硬背,其实你主要掌握了状态这个关键字就不需要死记硬背。状态就是和其他数据有关系。我们可以看方法的参数,如果是一个参数的,就是无状态操作,因为只和自己有关,其他的就是有状态操作。如map/filter方法,只有一个参数就是自己,就是无状态操作;而distinct/sorted就是有状态操作,因为去重和排序都需要和其他数据比较,理解了这点,就不需要死记硬背了!

为什么要知道有状态和无状态操作呢?在多个操作的时候,我们需要把无状态操作写在一起,有状态操作放到最后,这样效率会更加高。

1.4 获取流

java.util.stream.Stream是Java8新加入的流接口。 获取流非常简单

  • 所有的Collection集合都可以通过stream默认方法获取流。 (例如:list.stream())
  • Stream接口的静态方法of可以获取数组对应的流。 (例如:Stream.of(array))

1.5 常用方法

1.5.1 中间操作(延时方法)

返回值类型仍然是Stream接口自身类型的方法

1.5.1.1 过滤:filter

可以通过filter方法将一个流转换成另一个子集流
Stream filter(Predicate<? super T >predicate);
接收一个Predicte函数式接口参数作为筛选条件。
不知道查看上一个博客

public static void testFilter(){
        List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        //截取所有能被2整除得数据
        List<Integer> collect = integers.stream().filter(i -> i % 2 == 0).collect(Collectors.toList());
        System.out.println("collect = " + collect);
    }
1.5.1.2 映射: map

如果需要将流中的元素映射到另一个流中,可以使用map方法。
Stream map(Function <? super T, ? extends R > map);
接收一个Function函数式接口参数作为参数。

public static void main(String[] args) {
        //自己建好得一个获取对象list得方法
        List<Dish> dishList = Dish.getDishList();
        //获取每一道菜得名称  并放到一个list中
        List<String> collect = dishList.stream().map(Dish::getName).collect(Collectors.toList());
        //collect = [pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon]
        System.out.println("collect = " + collect);
    }
1.5.1.3 distinct: 去重

该操作会返回一个元素各异(根据流所生成元素的hashCode和equals方法实现)的流。

public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
        List<Integer> collect = numbers.stream().distinct().collect(Collectors.toList());
        System.out.println("collect = " + collect);
}

结果: collect = [1, 2, 3, 4]

1.5.1.4 sorted :排序

对流中得数据进行排序,可以以自然序或着用Comparator 接口定义的排序规则来排序一个流。Comparator
能使用lambada表达式来初始化,还能够逆序一个已经排序的流。

public static void main(String[] args) {
    List<Integer> integers = Arrays.asList(5, 8, 2, 6, 41, 11);
    //排序默认为顺序  顺序 = [2, 5, 6, 8, 11, 41]
    List<Integer> sorted = integers.stream().sorted().collect(Collectors.toList());
    System.out.println("顺序 = " + sorted);
    //逆序    逆序 = [41, 11, 8, 6, 5, 2]
    List<Integer> reverseOrder = integers.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
    System.out.println("逆序 = " + reverseOrder);
    //也可以接收一个lambda
    List<Integer> ages = integers.stream().sorted(Comparator.comparing(User::getAge)).collect(Collectors.toList());
}
1.5.1.5 limit: 截取

该方法会返回一个不超过给定长度的流。

public static void testLimit(){
        List<Integer> integers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
        //截取流中得前三个元素  collect = [1, 2, 1]
        List<Integer> collect = integers.stream().limit(3).collect(Collectors.toList());
        System.out.println("collect = " + collect);
    }
1.5.1.6 skip: 舍弃

该方法会返回一个扔掉了前面n个元素的流。如果流中元素不足n个,则返回一个空流。

public static void testSkip(){
        List<Integer> integers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
        //丢掉流中得前三个元素  collect = [3, 3, 2, 4]
        List<Integer> collect = integers.stream().skip(3).collect(Collectors.toList());
        System.out.println("collect = " + collect);
}
1.5.1.7 flatMap: 扁平化

该方法可以让你把一个流中的每个值都换成另一个流,然后把所有的流都链接起来成为一个流。 给 定 单 词 列表[“Hello”,“World”] ,你想要返回列表 [“H”,“e”,“l”, “o”,“W”,“r”,“d”],你可能会认为这很容易,通过map你可以把每个单词映射成一张字符表,然后调用 distinct来过滤重复的字符,但是这个方法的问题在于,传递给 map 方法的Lambda为每个单词返回了一个 String[] (String列表)。因此, map 返回的流实际上是Stream<String[]> 类型的。而你真正想要的是用Stream来表示一个字符流。 正确写法应该是通过flatMap对其扁平化并作出对应处理。
我的理解是假如你的集合流中包含子集合,那么使用flatMap可以返回该子集合的集合流.

public static void main(String[] args) {
        String[] words = {"Hello", "World"};
        List<String> collect = Stream.of(words).        //数组转换流
                map(w -> w.split("")).  //去掉“”并获取到两个String[]
                flatMap(Arrays::stream).        //方法调用将两个String[]扁平化为一个stream
                distinct().                     //去重    
                collect(Collectors.toList());
        //collect = [H, e, l, o, W, r, d]
        System.out.println("collect = " + collect);
    }
}

1.5.2 终止操作(终结方法)

循环 forEach
计算 min、max、count、average
匹配 anyMatch、allMatch、noneMatch、findFirst、findAny
汇聚 reduce
收集器 collect

1.5.2.1 collect:收集

从上面得代码已经可以看出来,collect是将最终stream中得数据收集起来,最终生成一个list,set,或者map。

public static void main(String[] args) {
        List<Dish> dishList = Dish.getDishList();
        //list
        List<Dish> collect = dishList.stream().limit(2).collect(Collectors.toList());
        //set
        Set<Dish> collect1 = dishList.stream().limit(2).collect(Collectors.toSet());
        //map
        Map<String, Dish.Type> collect2 = dishList.stream().limit(2).collect(Collectors.toMap(Dish::getName, Dish::getType));
}

这里面生成map得toMap方法有三个重载,传入得参数都不同,这里使用得是传入两个Function类型得参数。当然,Collectors的功能还不止这些,下面的收集器中会有其他的详解。

1.5.2.2 anyMatch

anyMatch方法可以回答“流中是否有一个元素能匹配到给定的谓词”。会返回一个boolean值。

public class AnyMatch {
    public static void main(String[] args) {
        List<Dish> dish = Dish.getDish();
        boolean b = dish.stream().anyMatch(Dish::isVegetarian);
        System.out.println(b);
    }
}
1.5.2.3 allMatch

allMatch方法和anyMatch类似,校验流中是否都能匹配到给定的谓词

class AllMatch{
    public static void main(String[] args) {
        List<Dish> dish = Dish.getDish();
        //是否所有菜的热量都小于1000
        boolean b = dish.stream().allMatch(d -> d.getCalories() < 1000);
        System.out.println(b);
    }
}
1.5.2.4 noneMatch

noneMatch方法可以确保流中没有任何元素与给定的谓词匹配。

class NoneMatch{
    public static void main(String[] args) {
        List<Dish> dish = Dish.getDish();
        //没有任何菜的热量大于等于1000
        boolean b = dish.stream().allMatch(d -> d.getCalories() >= 1000);
        System.out.println(b);
    }
}

anyMatch,noneMatch,allMatch这三个操作都用到了所谓的短路。

1.5.2.5 findAny

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

class FindAny{
    public static void main(String[] args) {
        List<Dish> dish = Dish.getDish();
        Optional<Dish> any = dish.stream().filter(Dish::isVegetarian).findAny();
        System.out.println("any = " + any);
    }
}
1.5.2.6 findFirst

findFirst方法能找到你想要的第一个元素。

class FindFirst{
        public static void main(String[] args) {
            List<Dish> dish = Dish.getDish();
            Optional<Dish> any = dish.stream().filter(Dish::isVegetarian).findFirst();
            System.out.println("any = " + any);
        }
    }
1.5.2.7 归约 reduce

此类查询需要将流中所有元素反复结合起来,得到一个值,比如一个 Integer
。这样的查询可以被归类为归约操作(将流归约成一个值)。用函数式编程语言的术语来说,这称为折叠(fold),因为你可以将这个操
作看成把一张长长的纸(你的流)反复折叠成一个小方块,而这就是折叠操作的结果。

public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 2, 3, 6, 8);
        //求list中的和,以0为基数
        Integer reduce = integers.stream().reduce(0, (a, b) -> a + b);
        //Integer的静态方法
        int sum = integers.stream().reduce(0, Integer::sum);
        System.out.println("reduce = " + reduce);
    }
1.5.2.8 最大值和最小值
public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 2, 3, 6, 8);
        Optional<Integer> min = integers.stream().reduce(Integer::min);
        System.out.println("min = " + min);
        Optional<Integer> max = integers.stream().reduce(Integer::max);
        System.out.println("max = " + max);
    }
1.5.2.9 Collectors
public static void main(String[] args) {
        List<Dish> dish = Dish.getDish();
        //创建一个Comparator来进行比较  比较菜的卡路里
        Comparator<Dish> dishComparator = Comparator.comparingInt(Dish::getCalories);
        //maxBy选出最大值
        Optional<Dish> collect = dish.stream().collect(Collectors.maxBy(dishComparator));
        System.out.println("collect = " + collect);
        //选出最小值
        Optional<Dish> collect1 = dish.stream().collect(Collectors.minBy(dishComparator));
        System.out.println("collect1 = " + collect1);
    }
1.5.2.10 汇总 summingInt

Collectors.summingInt 。它可接受一个把对象映射为求和所需 int 的函数,并返回一个收集器。

public static void main(String[] args) {
        List<Dish> dish = Dish.getDish();
        //计算总和
        int collect = dish.stream().collect(Collectors.summingInt(Dish::getCalories));
        System.out.println("collect = " + collect);
    }
1.5.2.11 平均数 averagingInt
public static void main(String[] args) {
        List<Dish> dish = Dish.getDish();
        //计算平均数
        Double collect = dish.stream().collect(Collectors.averagingInt(Dish::getCalories));
        System.out.println("collect = " + collect);
    }
1.5.2.12 连接字符串 joining
public static void main(String[] args) {
        List<Dish> dish = Dish.getDish();
        String collect = dish.stream().map(Dish::getName).collect(Collectors.joining());
        System.out.println("collect = " + collect);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值