zoukankan      html  css  js  c++  java
  • 9102年了,你还在用for循环操作集合?

    本文首发于cdream的个人博客,点击获得更好的阅读体验!

    欢迎转载,转载请注明出处。

    前段时间公司书架多了一本《Java8 实战》,毕竟久闻lambda的大名,于是借来一阅。这一看,简直是惊为天人啊,lambda,stream,java8里简直是满脑子骚操作,看我的一愣一愣的。我甚至是第一次感觉到了什么叫优雅

    image-20190224175258572

    本文主要介绍java8中的流处理,看看java8是怎么愉快的玩耍集合的,让我们来一起感受java8的魅力吧!

    我就随便举个例子,看看Stream有多优雅。

    // 对苹果按颜色汇总并绩数量
    Map<String, Long> appleCount = apples.stream()
        .collect(groupingBy(Apple::getColor, counting()));
    // 过滤掉颜色为黑色的苹果,并汇总好苹果的总金额
    Double sum = apples.stream()
        .filter(i->"black".equals(i.getColor()))
        .collect(toList);
    

    一、lambda表达式

    虽然本文重点是stream,但是stream中需要传递lambda表达式,所以简单介绍一下lambda表达式。lambda表达式其实就是匿名函数(anonymous function),是指一类无需定义标识符的函数或子程序。

    java中匿名函数的表现形式,只留下入参和方法体中的内容

    // 普通函数
    public void run(String s){
        System.out.print(s+"哈哈");
    }
    // 我不要名字啦!!!
    (s)->System.out.print(s+"哈哈")
    

    诶,过去我们都用对象调方法的,你弄这个没名的东西啥时候用啊?

    java中我们通过函数式接口来使用这种匿名函数。

    函数式接口
    1.java中只包含一个未实现方法的接口。其中可以有与Object中同名的方法和默认方法(java8中接口方法可以有默认实现)。
    2.java中函数式接口使用@FunctionalInterface进行注解。Runnable、Comparator都是函数式接口。
    3.java.util.function包下为我们提供很多常用的函数式接口,例如Function等。

    用法举例:

    // 实现Runnable中的run方法,替代匿名内部类。
    Runnable r = ()->System.out.print("哈哈");
    // 作为参数传递。
    new Thread(()-> System.out.println("haha")).start();
    
    ArrayList<Apple> list = new ArrayList<>();
    list.forEach(i-> System.out.println(i.getWeight()));
    
    
    // 简化策略模式
    public static List<Apple> filterApples(List<Apple> inventory,ApplePredicate p){
        List<Apple>  apples = new ArrayList<>();
        for(Apple apple : inventory){
            if(p.test(apple)){
                apples.add(apple);
            }
        }
        return apples;
    }
    public class BigApple implement ApplePredicate{
        @Override
        public boolean test(Apple a){
            if(a.getWeight>10){
                return a
            }
        }
    }
    // 这是个简单的策略模式,根据用户的需要,创建不同的接口ApplePredicate实现类,调用时传入不同的实现类就可以,但问题是如果需求过多,创建的实现类也会很多,过于臃肿不方便管理。
    xx.filterApple(inventory,new BigApple);
    // 使用lambda表达式,不在需要创建BigApple类
    xx.filterApple(inventory,i->(i.getWeight>10));
    

    使用lambda表达式可以简化大量的模板代码,并且可以向方法直接传递代码。

    总之

    方法出参入参来自函数式接口
    //入参s,返回void
    (s)->System.out.println(s);
    //入参空,返回void
    ()->System.out.print("haha");
    //入参i,返回i+1
    i->i+1
    //后面写代码块
    apple->{if(apple.getWeiht>5) return "BIG";
            else return "small";
           }
    

    好了,不多啰嗦了,如果感兴趣推荐下面的文章或《Java8实战》的前三章。

    二、Stream

    流是什么?

    Java API的新成员,它允许你使用声明式方式处理数据集合(类似sql,通过查询语句表达,而不是临时编写一个实现)。

    如果有人说lambda表达式不易于理解,那还勉强可以接受(其实过于复杂的lambda缺失不好阅读,但通常lambda不会做太复杂的实现),但流真的非常的易懂易用。这个语法糖真的是甜死了。

    注意事项:
    1.流只能使用一次,遍历结束就代表这个流被消耗掉了
    2.流对集合的操作属于内部迭代,是流帮助我们操作,而不是外部迭代
    3.流操作包含:数据源,中间操作链,终端操作三个部分。

    基础流操作

    List<Double> collect = list.stream()
            // 过滤掉黑色的苹果
            .filter(i -> "black".equals(i.getColor()))
            // 让苹果按照重量个价格排序
            .sorted(Comparator.comparing(Apple::getWeight)
                    .thenComparing(i->i.getPrice()))
            // 筛选掉重复的数据
            .distinct()
            // 只要苹果的价格
            .map(Apple::getPrice)
            // 只留下前两条数据
            .limit(2)
            // 以集合的形式返回
            .collect(toList());
    // 循环打印列表中元素
    list.forEach(i->System.out.print(i));
    

    Apple::getPrince<=>i -> i.getPrince()可以看做是仅涉及单一方法的语法糖,效果与lambda表达式相同,但可读性更好。

    同理

    下面列表为常见操作

    中间

    操作 类型 作用 函数描述 函数
    filter 中间 过滤 T -> boolean Predicate
    sorted 中间 排序 (T,T)->int Comparator
    map 中间 映射 T->R Function<T,R>
    limit 中间 截断
    distinct 中间 去重,根据equals方法
    skip 中间 跳过前n个元素

    终端

    操作 类型 作用
    forEach 终端 消费流中的每个元素,使用lambda进行操作
    count 终端 返回元素个数,long
    collect 终端 将流归约成一个集合,如List,Map甚至是Integer

    筛选与切片

    List<String> strings = Arrays.asList("Hello", "World");
    List<String> collect1 = strings.stream()
        // String映射成String[]
        .map(i -> i.split(""))
        // Arrays::Stream 数据数组,返回一个流String[]->Stream<String>
        // flatMap各数组并不分别映射成一个流,而是映射成流的内容 Stream<String>->Stream
        .flatMap(Arrays::stream)
        .collect(toList());
    System.out.println(collect);
    ----->输出 [H, e, l, l, o, W, o, r, l, d]
    

    归约操作reduce

    List<Integer> integers = Arrays.asList(12, 3, 45, 3, 2,-1);
    // 有初始值的叠加操作
    Integer reduce = integers.stream().reduce(3, (i, j) -> i + j);
    Integer reduce2 = integers.stream().reduce(5, (x, y) -> x < y ? x : y);
    // 无初始值的叠加操作
    Optional<Integer> reduce1 = integers.stream().reduce((i, j) -> i + j);
    // 无初始值的最大值
    Optional<Integer> reduce4 = integers.stream().reduce(Integer::min);
    // 无初始值的最大值
    Optional<Integer> reduce5 = integers.stream().reduce(Integer::max);
    // 求和
    Optional<Integer> reduce6 = integers.stream().reduce(Integer::sum);
    

    reduce做的事情是取两个数进行操作,结果返回取下一个数操作,以次类推。

    Optional是java8引入的新类,避免造成空指针异常,在集合为空时,结果会包在Optional中,可以用isPresent()方法来判断是否为空值。

    无初始值的情况下可能为空,故返回Optional

    中间

    操作 类型 作用 函数描述 函数
    flatmap 中间 使通过的流返回内容 T -> boolean Predicate

    终端

    操作 类型 作用
    anyMatch 终端 返回boolean,判断是否有符合条件内容
    noneMatch 终端 返回boolean,判断是否无符合条件内容
    allMatch 终端 返回boolean,判断是全为符合条件内容
    findAny 终端 Optional,随机找一个元素返回
    findFirst 终端 Optional,返回第一个元素
    reduce 终端 Optional (T,T)->T 归约操作

    数值流

    包装类型的各种操作都会有拆箱操作和装箱操作,严重影响性能。所以Java8为我们提供了原始数值流。

    // 数值流求平均值
    OptionalDouble average = apples.stream()
            .mapToDouble(Apple::getPrice)
            .average();
    // 数值流求和
    OptionalDouble average = apples.stream()
        .mapToDouble(Apple::getPrice)
        .sum();
    // 数值流求最大值,没有则返回2
    double v = apples.stream()
        .mapToDouble(Apple::getPrice)
        .max().orElse(2);
    // 生成随机数
    IntStream s = IntStream.rangeClosed(1,100);
    

    下面列表为常见数值流操作操作

    中间

    操作 类型 作用
    rangeClosed(1,100) 中间 生成随机数(1,100]
    range(1,100) 中间 生成随机数(1,100)
    boxed() 中间 包装成一般流
    mapToObj 中间 返回为对象流
    mapToInt 中间 映射为数值流

    终端,终端操作与List一般流类似

    构建流

    1. 值创建

      Stream<String> s = Stream.of("java","python");
      
    2. 数组创建

      int[]  i = {2,3,4,5};
      Stream<int> = Arrays.stream(i);
      
    3. 由文件生成,NIO API已经更新,以便利用Stream API

      Stream<String> s = Files.lines(Paths.get("data.txt"),Charset.defaultCharset());
      
    4. 由函数创建流:无限流

      // 迭代
      Stream.iterate(0,n->n+2)
      	.limit(10)
          .forEach(System.out::println);
      // 生成,需要传递实现Supplier<T>类型的Lambda提供的新值
      Stream.generate(Math.random)
          .limit(5)
          .forEach(System.out::println);
      

    三、总结

    至此,本文讲述了常见的流操作,目前排序、筛选、求和、归约等大多数操作我们都能实现了。与过去相比,操作集合变的简单多了,代码也变的更加简练明了。

    目前Vert.x,Spring新出的WebFlux都通过lambda表达式来简化代码,不久的将来,非阻塞式框架的大行其道时,lambda表达式必将变的更加重要!

    至于开篇见到的分组!!!下篇文章见~


    参考资料:

    1. Java8 实战,Raoul-Gabriel Urma,Mario Fusco,Alan Mycroft
  • 相关阅读:
    JAVA内存溢出解析(转)
    检查SQL Server 2005的索引密度和碎片信息(转)
    GWT 学习总结
    支付宝techday分享成长、团队、信任
    一位年轻董事长给年轻人的30条忠告
    Oracle 培训材料
    利用JProfiler对应用服务器内存泄漏问题诊断一例(转)
    oracle 笔记
    JProfiler 使用说明
    最疼你的人是谁
  • 原文地址:https://www.cnblogs.com/cdream-zs/p/10504499.html
Copyright © 2011-2022 走看看