Java菜谱(三)——常用数据结构转换及处理

2021-04-21  本文已影响0人  程序员吉森

今天的菜品是这样的:

List、Set、数组、Map这些常用的数据结构之间是怎么转换的?

太长不看版

下面是正文:

List、Set和Array之间的互转

List和Set互转

List和Set之间的相互转换主要靠List和Set的有参构造器。这种方法同样适用于List或Set不同实现类之间的互相转换。

List/Set和Array互转

数组转List/Set时,首先可以利用Arrays.asList方法转换为List,这时转成的List对象是一个名为ArrayList的内部类的对象,它是原数组的视图,有很多操作不支持。因此我们需要再利用上一节中的方法用List/Set的构造器再包一层。List/Set转为数组时,可以直接用数据的toArray方法,由于Java中泛型的限制,toArray方法需要传一个数组作为参数,这个数组通常传一个新建的相应类型的0元素数组即可。

    Integer[] array = {1, 2, 3, 4, 5};
    
    // array -> list       
    List<Integer> listFromArray = new ArrayList<>(Arrays.asList(array));
    System.out.println(listFromArray);

    // list -> array
    Integer[] arrayFromList = listFromArray.toArray(new Integer[0]);
    System.out.println(Arrays.toString(arrayFromList));

    // list -> set
    Set<Integer> setFromList = new HashSet<>(listFromArray);
    System.out.println(setFromList);

    // set -> list
    List<Integer> listFromSet = new ArrayList<>(setFromList);
    System.out.println(listFromSet);

    // set -> array
    Integer[] arrayFromSet = setFromList.toArray(new Integer[0]);
    System.out.println(Arrays.toString(arrayFromSet));

    // array -> set
    Set<Integer> setFromArray = new HashSet<>(Arrays.asList(array));
    System.out.println(setFromArray);

List、Set、Array和流(Stream)的互转

Stream(流)是Jdk8开始引入的一个抽象概念,它并不是实体的数据结构,不保存对象,但是它可以在让要处理的元素在管道中传输,在管道的各个节点上进行筛选、排序、聚合等处理。可以把List等数据结构想象成水桶,Stream就是水管,我们可以把水桶中的水抽到水管中进行处理,但是处理过后一定要再用一个容器去容纳这些水。

List/Set和Stream互转

List/Set转为Stream,可以利用这些类本身的stream()方法。Stream转为List/Set时,可以利用Stream的collect方法,将Collectors.toList()或Collectors.toSet()作为参数。

Array和Stream互转

Array转为Stream,可以使用Arrays.Stream方法或者Stream.of方法,这些方法的参数都是泛型不定长参数,可以将数组作为参数传入。但是需注意,如果要转换的数组是基本数据类型(int、long和double),那么必须使用Arrays.Stream方法,因为该方法为基本数据类型设定了重载方法。如果使用Stream.of方法,整个数组会被当成一个元素,从而将数组转换为了包含一个元素的数组的流。另外注意,除了int、long和double外,其他基本数据类型没有对应的基本流。

    // array -> stream
    Integer[] array = {1, 2, 3, 4, 5};
    int[] intArray = {1, 2, 3, 4, 5};

    // 1. Stream.of没有为int[]单独设置重载方法,因此整个int[]对象会被当做一个参数
    Stream<Integer> stream1 = Stream.of(array);
    Stream<int[]> stream2 = Stream.of(intArray);

    // 2. Arrays.stream为int[]单独设置了重载方法,因此会被转换成基本数据流IntStream(流里存的元素是int类型)
    Stream<Integer> stream3 = Arrays.stream(array);
    IntStream intStream = Arrays.stream(intArray);

    // stream -> array
    Integer[] arrayFromStream = stream1.toArray(Integer[]::new);
    int[] intArrayFromStream = intStream.toArray();

    // list -> stream
    List<Integer> listFromArray = new ArrayList<>(Arrays.asList(array));
    Stream<Integer> streamFromList = listFromArray.stream();

    // stream -> list
    List<Integer> listFromStream = streamFromList.collect(Collectors.toList());

    // set -> stream
    Set<Integer> setFromArray = new HashSet<>(Arrays.asList(array));
    Stream<Integer> streamFromSet = setFromArray.stream();

    // stream -> set
    Set<Integer> setFromStream = streamFromSet.collect(Collectors.toSet());

List转为Map

想要将List转为Map,我们可以先把集合转成流,再调用流的归并操作collect。collect方法的数据通过调用Collectors.toMap方法获取,该方法的两个参数分别传根据List中元素生成Map的key和value的策略。

    List<Student> students = new ArrayList<>();
    students.add(student);
    // 将list转为学生ID和学生对象的映射表
    Map<Integer, Student> studentMap = students.stream()
        .collect(Collectors.toMap(Student::getId, s->s));

数据分组及后续处理

想要对数据进行分组,我们可以先把集合转成流,再调用流的归并操作collect。collect方法的数据通过调用Collectors.groupingBy方法获取,groupingBy方法可以传一到两个参数,第一个参数为分组的依据,如果传第二个参数,则它代表分组后的后续(downstream)操作。以求学生平均分为例:

    List<Student> students = new ArrayList<>();

    // 求男/女学生的平均分
    Map<Integer, Double> averageScoreMap = students.stream()
            .collect(Collectors.groupingBy(Student::getGender, Collectors.averagingDouble(Student::getScore)));

数据的筛选、按多个条件排序

filter

如果我们只需要获得男学生的平均分,就不需要进行分组操作了,可以直接使用filter方法进行过滤操作,如下:

    final Double average = students.stream()
                .filter(s -> s.getGender() == 1)
                .collect(Collectors.averagingDouble(Student::getScore));

过滤操作传入的参数是一个Predicate类型的对象,它代表元素保留下来的条件。

sort

如果需要对集合进行排序,我们可以使用List的sort方法,也可以使用Stream的sorted方法。这些方法要求我们传一个Comparator对象,作为排序的依据。如:

    students.sort((s1, s2) -> {
            double diff = s1.getScore() - s2.getScore();
            return diff > 0 ? 1 : diff == 0 ? 0 : -1;
        });

Comparator.comparing

如果我们是根据类中的某个字段对某个类的对象进行排序的话,我们可以使用Comparator.comparing这个静态方法构造Comparator对象。因此,上述代码可以简化为:

    students.sort(Comparator.comparing(Student::getScore));

Comparator的thenComparing方法

有时我们需要对多个字段进行排序,这时我们可以利用Comparator对象的thenComparing方法构造一个用于多字段排序的比较器,方法中需要传一个新的比较器或比较策略。如对学生先按班级再按分数进行排序:

    students.sort(Comparator.comparing(Student::getClassNumber)
    .thenComparing(Student::getScore));

Comparator的reversed方法

Comparator.comparing默认是按自然排序,即由小到大排序。有的时候我们需要按从大到小的顺序进行排序,此时我们可以调用Comparator的reversed方法,如对学生分数按由高到低排序:

    students.sort(Comparator
    .comparing(Student::getScore)
    .reversed());

本文介绍了常用数据结构相互转换的方法以及一些数据的常见处理方式。今天的文章就到这里,有什么想要聊的话题,欢迎留言。喜欢文章的同学也可以鼓励我一下哦~

上一篇下一篇

猜你喜欢

热点阅读