java8新特性之Stream


Stream简介:
    java8随着lambda表达式的加入,又加入了Stream(流),什么是流呢?书上是这样定义的“从支持数据处理操作的源生成的元素序列”,
元素序列——就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值。
源——流会使用一个提供数据的源,如集合、数组或输入/输出资源。
数据处理操作——流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,如filter、map、reduce、find、match、sort等。

话不多说,让我们看一个实例。找出了卡路里大于350的所有饮料

List<Drink> drinkList = Arrays.asList(
                new Drink("cocaCalo", Drink.Type.CARBONIC_ACID,300),
                new Drink("sprite", Drink.Type.CARBONIC_ACID,330),
                new Drink("fanta", Drink.Type.FRUIT_JUICE,350),
                new Drink("mintueMaid", Drink.Type.FRUIT_JUICE,430),
                new Drink("coconut", Drink.Type.FRUIT_JUICE,370),
                new Drink("yiLi", Drink.Type.MILK,550),
                new Drink("wangZai", Drink.Type.MILK,500),
                new Drink("IcedRedTea", Drink.Type.TEA,280),
                new Drink("greenTea", Drink.Type.TEA,250)
        );

//使用Stream写法
List<Drink> list = drinkList.stream().filter(d -> d.getCalorie() > 350).collect(toList());

//使用传统的写法
List<Drink> newList = new ArrayList<>();
        for (int i=0;i<drinkList.size();i++){
            if(drinkList.get(i).getCalorie()>350){
                newList.add(drinkList.get(i));
            }
        }

Stream语法:
    1.stream()
    使用流的时候,要先把数据源转换成流,Collection接口提供了一个stream()方法,可以把集合转换成流。
    Stream<Drink> stream = drinkList.stream();
  
    2.筛选filter()
    Stream<Drink> stream2 = drinkList.stream().filter(d -> d.getCalorie() > 350);
    filter()接受一个谓词Predicate,Predicate是一个函数式接口,函数式接口可以使用lambda表达四“d -> d.getCalorie() > 350”,如果对lambda表达式
    还没有了解的同学可以看一下上一篇介绍的lambda表达式。
    
    3.映射map()
    Stream<String> stream3 = drinkList.stream().filter(d -> d.getCalorie() > 350).map(d -> d.getName());
    map()的参数是Function函数式接口,“d -> d.getName()”这个lanbda表达式把每一个Drink对象映射成对象的名字
    
    4.截短流limit()
    Stream<String> stream4 = drinkList.stream().filter(d -> d.getCalorie() > 350).map(d -> d.getName()).limit(3);
    limit()的参数是一个long参数3,获取流中的3个元素。
    
    5.跳过流元素skip()
    Stream<Drink> stream5 = drinkList.stream().filter(d -> d.getCalorie() > 350).skip(2);
    
    6.collect()
    我们可以看到以上的方法返回的类型都是Straen<T>类型,没有返回集合类型,因此我们称以上的流操作为中间操作。
    我们要把流又变成集合就需要使用终端操作,collect()方法就是一个终端操作,它接受的参数是一个收集器
    List<String> list1 = drinkList.stream().filter(d -> d.getCalorie() > 350).map(d -> d.getName()).skip(2).limit(3).collect(toList());
    
    7.count()
    long count = drinkList.stream().filter(d -> d.getCalorie() > 500).count();
    count()方法也是一个终端操作,返回了卡路里大于500的饮料个数。
    以上是非常使用流过程中很常见的操作,还有一些操作我们结合一些例子来讲解。
    
    8.flatMap():流扁平化。就是把多个流化成一个流
    把["Hello","World"]变成[H, e, l, o, W, r, d];
    List<String> list2 = Arrays.asList("Hello", "World");
    List<String> list3 = list2.stream().map(str -> str.split("")).flatMap(Arrays::stream).distinct().collect(toList());
    list3 = [H, e, l, o, W, r, d];
    首先通过map()把"Hello"映射成H,e,l,l,o,"World"映射成W,o,r,l,d。但是类型是Stream<String[]>,我们还需要变成Stream<String>,
    因此使用flatMap(),跟map()一样,参数是Function函数式接口,Arrays::stream,这是方法引用,就是Arrays类的stream()方法,这个方法可以把一个数组
    转变成流 String[] strs = new String[]{"Hello"};Stream<String> stream7 = Arrays.stream(strs);。因此通过flatMap()操作,我们就把String[]变成
    Stream<String>,然后再通过distinct()方法把重复的去掉,这个跟MySQL数据库的distinct()方法一样。
    
    9.查找和匹配
    题目:找出一瓶属于茶类的一瓶饮料。
    9.1 findAny(); 
    Optional<Drink> tea = drinkList.stream().filter(d -> Drink.Type.TEA.equals(d.getType())).findAny();
    tea.ifPresent(t -> System.out.println(t));
    这里返回一个Optional类,这里简单的介绍一下Optional类,Optional<T>类(java.util.Optional)是一个容器类,代表一个值存在或不存在。
    也是在java8中提出来的,用来取代null的,我们会在下一篇详细的介绍Optional;Optional类的isPresent()方法,如果存在就打印出这个饮料。
    9.2 findFrist()
    Optional<Drink> result = drinkList.stream().filter(d -> d.getCalorie() > 450).findFirst();
    result.ifPresent(System.out::println);
    9.3 allMatch,匹配所有元素
        boolean b1 = drinkList.stream().anyMatch(d -> d.getCalorie() > 500); //b1 = true
    9.4 anyMatch,匹配任意一个元素
        boolean b2 = drinkList.stream().allMatch(d -> d.getCalorie() > 500); //b2 = false
    9.5 noneMatch,一个元素都不匹配
        boolean b3 = drinkList.stream().noneMatch(d -> d.getCalorie() > 500); //b3 = false

    10.reduce(),归约操作。
    List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5);
    Integer sum = intList.stream().reduce(0, (a, b) -> a + b); //sum = 15;
    归约操作就是将Stream的每个元素都用加法运算符反复迭代来得到结果,跟下面的方法一样。
    int sum = 0; 
    for (int x : numbers) { 
        sum += x; 
    }
    
    Integer max = intList.stream().reduce(0, (a, b) -> a > b ? a : b); //max=5
    Integer min = intList.stream().reduce(0,(a, b) -> a > b ? b : a);  //min=0,这里不太准确,因为我们给了一个初始变量0,它比数组所有的数都小。
    Optional<Integer> min = intList.stream().reduce((a, b) -> a > b ? b : a);
    min1.ifPresent(System.out::println);//打印出1,因此我们可以知道,如若不给一个初始值给reduce(),就会返回一个Optional对象,因为不知道有没有这个值。
    

    11.数值流
    题目:求出所有饮料的所有卡路里的总和;
    Integer calorieSum = drinkList.stream().map(d -> d.getCalorie()).reduce(0, Integer::sum); //calorieSum = 3360;这里有一个暗含的装箱成本,
    每个Integer都要拆成原始类型再求和。
    int calorieSum1 = drinkList.stream().mapToInt(d -> d.getCalorie()).sum();//使用mapToInt就可以把流转换成一个IntStream。然后直接使用sum()方法就可以求和。还有max(),min()等方法
    数值流又IntStream,DoubleStream,LongStream(),我们可以通过mapToInt(),mapToDouble(),把对象流转变成数值流。我们也可以通过boxed()方法把数值流转变成对象流
    下面我们通过一个例子来好好总结一下数值流的应用。相信“勾股定理”大加一定会有深刻印象,(3,4,5)就是一组勾股数。
    题目:打印出从1-100之间的5组勾股数。
    IntStream还有range(n,m)和rangeClosed(n,m)两个方法,就是产生数值流。区别就是rangeClosed()包含m,range()不包含m.
    Stream<int[]> gouGuShu = IntStream.rangeClosed(1, 100).boxed()
                .flatMap(i -> IntStream.rangeClosed(i, 100).filter(j -> (Math.sqrt(i * i + j * j)) % 1 == 0)
                        .mapToObj(j -> new int[]{i, j, (int) Math.sqrt(i * i + j * j)}));
    
    List<int[]> gouGuShuList = gouGuShu.limit(5).collect(Collectors.toList());
    gouGuShuList.forEach(g -> System.out.println(Arrays.toString(g)));
    打印出
    [3, 4, 5]
    [5, 12, 13]
    [6, 8, 10]
    [7, 24, 25]
    [8, 15, 17]
    

    12.收集器
    在前面介绍collect()方法的时候,我们就说他的参数是一个收集器。
    List<String> list1 = drinkList.stream().filter(d -> d.getCalorie() > 350).map(d -> d.getName()).skip(2).limit(3).collect(toList());
    toList()方法是java.util.stream.Collectors类的一个方法。返回一个返回一个把流转换成List的收集器。还有toSet()等。
    long count = drinkList.stream().filter(d -> d.getCalorie() > 500).collect(counting());
    也可以写成:
    long count = drinkList.stream().filter(d -> d.getCalorie() > 500).count();
    找出卡路里最大饮料
    Optional<Drink> maxCalorieDrink = drinkList.stream().collect(maxBy(Comparator.comparing(Drink::getCalorie)));
    卡路里最小的饮料
    Optional<Drink> maxCalorieDrink = drinkList.stream().collect(minBy(Comparator.comparing(Drink::getCalorie)));
    把所有饮料的卡路里求和
    Integer sumCalories = drinkList.stream().collect(summingInt(Drink::getCalorie));
    求出所有饮料的卡路里平均值
    Double averageCalorie = drinkList.stream().collect(averagingInt(Drink::getCalorie));
    把所有饮料的名字都连接起来
    String drinkName = drinkList.stream().map(d -> d.getName()).collect(Collectors.joining("+"));
    以上的所有收集器都可以使用reducing()方法来造出来
    Integer sumCalories1 = drinkList.stream().collect(reducing(0, Drink::getCalorie, (a, b) -> a + b));
    把饮料按各个类型分组
            Map<Drink.Type, List<Drink>> drinkByType = drinkList.stream().collect(groupingBy(Drink::getType));
    可以得到如下分组:
{
 TEA=[Drink{name='IcedRedTea', type=TEA, Calorie=280}, Drink{name='greenTea', type=TEA, Calorie=250}],
 FRUIT_JUICE=[Drink{name='fanta', type=FRUIT_JUICE, Calorie=350}, Drink{name='mintueMaid', type=FRUIT_JUICE, Calorie=430}, Drink{name='coconut', type=FRUIT_JUICE, Calorie=370}],
 CARBONIC_ACID=[Drink{name='cocaCalo', type=CARBONIC_ACID, Calorie=300}, Drink{name='sprite', type=CARBONIC_ACID, Calorie=330}], 
 MILK=[Drink{name='yiLi', type=MILK, Calorie=550}, Drink{name='wangZai', type=MILK, Calorie=500}]
 }
 
 按卡路里的高低来分大于400,算是高,小于400算是低。
  Map<String, List<Drink>> drinkByCalorie = drinkList.stream().collect(groupingBy(drink -> {
            if (drink.getCalorie() > 400) return "Hiht";
            else return "Low";
        }));

  得到如下的分组:
  {Low=[Drink{name='cocaCalo', type=CARBONIC_ACID, Calorie=300}, Drink{name='sprite', type=CARBONIC_ACID, Calorie=330}, Drink{name='fanta', type=FRUIT_JUICE, Calorie=350}, Drink{name='coconut', type=FRUIT_JUICE, Calorie=370}, Drink{name='IcedRedTea', type=TEA, Calorie=280}, Drink{name='greenTea', type=TEA, Calorie=250}], 
      Hiht=[Drink{name='mintueMaid', type=FRUIT_JUICE, Calorie=430}, Drink{name='yiLi', type=MILK, Calorie=550}, Drink{name='wangZai', type=MILK, Calorie=500}]
  }
  
 多级分组,按饮料的类型和卡路里的高低分组
     Map<String, Map<Drink.Type, List<Drink>>> drinkByCalorieAndType = drinkList.stream().collect(groupingBy(drink -> {
            if (drink.getCalorie() > 400) return "Higt";
            else return "Low";
        }, groupingBy(Drink::getType)));
        得到如下分组:
    {
        Low={
            TEA=[Drink{name='IcedRedTea', type=TEA, Calorie=280}, Drink{name='greenTea', type=TEA, Calorie=250}], 
            FRUIT_JUICE=[Drink{name='fanta', type=FRUIT_JUICE, Calorie=350}, Drink{name='coconut', type=FRUIT_JUICE, Calorie=370}],
            CARBONIC_ACID=[Drink{name='cocaCalo', type=CARBONIC_ACID, Calorie=300}, Drink{name='sprite', type=CARBONIC_ACID, Calorie=330}]
            }, 
        Higt={FRUIT_JUICE=[Drink{name='mintueMaid', type=FRUIT_JUICE, Calorie=430}],
            MILK=[Drink{name='yiLi', type=MILK, Calorie=550}, Drink{name='wangZai', type=MILK, Calorie=500}]
            }
    }

这一篇我们介绍的都是顺序流,在下一篇我们会介绍并行流。

猜你喜欢

转载自blog.csdn.net/weixin_42248745/article/details/81436321