zoukankan      html  css  js  c++  java
  • Java集合操作 遍历list并转map

    本文主要介绍如何使用java对list进行遍历,并将list转换成map。主要涉及jdk8的stream操作和guava工具包操作集合,并涉及到guava工具包的实现说明。

    需求

    有一个student list集合,包含学号,身高,体重,需要遍历list,计算出BMI值,并将其转换成 学号 -> student 的map集合。

    Student.java
    
    class Student{
        private int id;
        private float weight;
        private float height;
        private float IMB;
    }
    
    计算IMB值
    
    Private Student calIMB(Student stu){
        int imb = stu.getWeight() / (stu.getHeight() * stu.getHeight());
        return stu.setIMB(imb);
    }
    

    jdk8 流式操作

    流式操作的代码非常简洁,一行代码即可搞定。

    List<Student> studentList;
    Map resultMap = studentList.stream().
    map(stu -> calIMB(stu)).
    collection(Collectors.toMap(stu::getId,stu -> stu,(s1,s2)-> s1));
    
    • Stream.map() 集合内的所有元素会被当成入参,运行一次入参方法。
    • Collectors.toMap(stu::getId,stu -> stu,(s1,s2)-> s1) 第一个参数是返回key,第二个参数是map对应的值,第三个参数为如果key重复时,选择哪个作为map的value。

    guava 工具类 Lists Maps

    在工作中会遇到java7或者更低版本的环境,无法使用stream流,但是for循环又太繁琐,可读性又差,这时候可以尝试使用google的guava工具包,guava提供了便捷的集合操作工具类,来看看如何实现这个需求呢。

    List<Student> studentList;
    studentList = Lists.transform(studentList, new Function<Student, Student>() {
        @Nullable @Override public Student apply(@Nullable Student stu) {
            return calIMB(stu);
        }
    }); //计算IMB值
    Map resultMap = Maps.uniqueIndex(studentList, new Function<Student, Integer>() {
        @Nullable @Override public Integer apply(@Nullable Student input) {
            return input.getId();
        }
    }); //list转map
    

    利用Lists.transform对list进行封装,使每个元素获取时都会执行第二个参数自定义function。再用Maps.uniqueIndex()将list转换成map,function的返回值就是map的key。

    此处需要注意的是,Lists.transform并不会执行function,返回的只是guava封装的TransformingRandomAccessList或TransformingSequentialList,在调用get或者Iterator获取元素时,才会对获取的元素执行function方法。

    Lists.transform 源码

    下面看一下Lists.transform做了些什么

    public static <F, T> List<T> transform(
        List<F> fromList, Function<? super F, ? extends T> function) {
    return (fromList instanceof RandomAccess)
        ? new TransformingRandomAccessList<F, T>(fromList, function)
        : new TransformingSequentialList<F, T>(fromList, function);
    }
    

    先判断list是否继承RandomAccess接口,RandomAccess是一个声明式接口,继承RandomAccess代表支持非顺序的获取元素,比如ArrayList就继承该接口,ArrayList.get(i)可以直接获取到第i个元素,而链表就需要i次迭代才可获取到数据。按RandomAccess的文档说明,for循环直接get元素进行迭代要比迭代器的操作更快一些,这点令我无法理解,理论上ArrayList的迭代耗时应该基本一致才对,也不知道有什么形式的列表满足这一含义。

    下面以TransformingRandomAccessList为例来看后续实现:

    private static class TransformingRandomAccessList<F, T>
          extends AbstractList<T> implements RandomAccess, Serializable {
    final List<F> fromList;
    final Function<? super F, ? extends T> function;
    
    TransformingRandomAccessList(
        List<F> fromList, Function<? super F, ? extends T> function) {
        this.fromList = checkNotNull(fromList);
        this.function = checkNotNull(function);
    }
    @Override public void clear() {
        fromList.clear();
    }
    @Override public T get(int index) {
        return function.apply(fromList.get(index));
    }
    @Override public Iterator<T> iterator() {
          return listIterator();
        }
        @Override public ListIterator<T> listIterator(int index) {
          return new TransformedListIterator<F, T>(fromList.listIterator(index)) {
            @Override
            T transform(F from) {
              return function.apply(from);
            }
          };
        }
    ...
    }
    

    TransformingRandomAccessList的构造方法只记录了list和function,并未进行其他操作,在调用get()方法或迭代器获取元素时,会调用function.apply()方法,多次获取同一个元素,自定义function会被重复调用多次。

    那么guava包内是否有方法可以类似stream流进行转换的时候就执行function获取结果,而不是获取元素时再执行的方法呢?

    FluentIterable

    FluentIterable 是guava集合类中常用的一个类,主要用于过滤、转换集合中的数据,它的使用更像是stream流:

    List<Student> studentList;
    Map resultMap = FluentIterable.from(studentList).transform(new Function<Student, Student>() {
        @Nullable @Override public Student apply(@Nullable Student input) {
            return calIMB(input);
        }
    }).uniqueIndex(studentList, new Function<Student, Integer>() {
        @Nullable @Override public Integer apply(@Nullable Student input) {
            return input.getId();
        }
    });
    

    和Lists、Maps工具类不同的是,每次transform操作都会对集合进行迭代,执行function,并将迭代后的Iterable通过from再次转换成FluentIterable,用以支持链式调用,看起来很像套娃,用起来也非常简洁。

    小结

    • 迭代集合进行转换操作,jdk8及以上版本用stream和lambda写起来轻松愉快。
    • 如果是低版本的,则可以考虑使用FluentIterable,代码也非常工整。
    • 若转换操作非常耗时,不会重复获取某个元素,并且只会使用集合的部分数据,使用Lists.transform进行转换也有助于性能提升。
  • 相关阅读:
    【APUE | 10】函数signal
    【C++ Primer | 15】C++虚函数表剖析②
    【C++ Primer | 15】C++类内存分布
    VMware虚拟机 Ubuntu 16.04 安装
    主题
    【C++ Primer | 15】构造函数与拷贝控制
    08 IO库
    001 库函数【01】
    DataTable序列化及反序列化Json
    DbHelper简单的使用
  • 原文地址:https://www.cnblogs.com/zzzdp/p/12891087.html
Copyright © 2011-2022 走看看