Java核心技术卷Ⅱ程序清单1-5

P21-22
程序清单1-5 collecting/CollectingIntoMaps.java


  1. 代码
  2. 分析
  3. 重要API

1.代码

import java.io.IOException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CollectingIntoMaps
{

   public static class Person
   {
      private int id;
      private String name;

      public Person(int id, String name)
      {
         this.id = id;
         this.name = name;
      }

      public int getId()
      {
         return id;
      }

      public String getName()
      {
         return name;
      }

    //覆写toString方法实现返回对象所属类和所有属性
      public String toString()
      {
         return getClass().getName() + "[id=" + id + ",name=" + name + "]";
      }
   }

   public static Stream people()
   {
   //返回含有三个Person对象元素的流
      return Stream.of(new Person(1001, "Peter"), new Person(1002, "Paul"),
            new Person(1003, "Mary"));
   }

   public static void main(String[] args) throws IOException
   {
   //将含有三个Person对象元素的流转换为键值对Map(键为id,值为name)并赋给idToName
      Map idToName = people().collect(
            Collectors.toMap(Person::getId, Person::getName));
      System.out.println("idToName: " + idToName);

    //将含有三个Person对象元素的流转换为键值对Map(键为id,值为Person对象)并赋给idToPerson
      Map idToPerson = people().collect(
            Collectors.toMap(Person::getId, Function.identity()));
      System.out.println("idToPerson: " + idToPerson.getClass().getName()
            + idToPerson);

    //将含有三个Person对象元素的流转换为TreeMap(键为id,值为Person对象)并赋给idToPerson
      idToPerson = people().collect(
            Collectors.toMap(Person::getId, Function.identity(), (
                  existingValue, newValue) -> {
               throw new IllegalStateException();
            }, TreeMap::new));
      System.out.println("idToPerson: " + idToPerson.getClass().getName()
            + idToPerson);

    //创建一个包含所有可支持的语言环境元素的流
      Stream locales = Stream.of(Locale.getAvailableLocales());

    //将含有语言环境元素的流转换为Map并赋给languageNames并去重
      Map languageNames = locales.collect(
         Collectors.toMap(
            Locale::getDisplayLanguage,
            l -> l.getDisplayLanguage(l), 
            (existingValue, newValue) -> existingValue));
      System.out.println("languageNames: " + languageNames);

    //将含有语言环境元素的流转换为Map并赋给countryLanguageSets
      locales = Stream.of(Locale.getAvailableLocales());
      Map> countryLanguageSets = locales.collect(
         Collectors.toMap(
            Locale::getDisplayCountry,
            l -> Collections.singleton(l.getDisplayLanguage()),

    //将两个Set对象a和b取并集
            (a, b) -> { // union of a and b
               Set union = new HashSet<>(a);
               union.addAll(b);
               return union;
            }));
      System.out.println("countryLanguageSets: " + countryLanguageSets);
   }
}

2.分析

我们直接进入main函数


1st PART:

Map idToPerson = people().collect(
            Collectors.toMap(Person::getId, Function.identity()));
      System.out.println("idToPerson: " + idToPerson.getClass().getName()
            + idToPerson);

解释一下这里的Function.identity():返回传入的对象。
官方文档的解释是:

static <T> Function<T,T>    identity()
Returns a function that always returns its input argument.

2nd PART:

      idToPerson = people().collect(
            Collectors.toMap(Person::getId, Function.identity(), (
                  existingValue, newValue) -> {
               throw new IllegalStateException();
            }, TreeMap::new));
      System.out.println("idToPerson: " + idToPerson.getClass().getName()
            + idToPerson);

Collectors.toMap的第四个引参Supplier mapFactory:传入一个构造器TreeMap::new,从而将收集得到的键值对转换为TreeMap返回。

而有关Collectors.toMap的第三个引参BiFunction remappingFunction

先解释一下含义:
(existingValue, newValue) -> {
    throw new IllegalStateException();
    }

IllegalStateException
在不合理或不正确时间内唤醒一方法时出现的异常信息。换句话说,即 Java 环境或 Java 应用不满足请求操作。
在这里即:如果有多个元素具有相同的键,那么就会存在冲突,就会抛出一个IllegalStateException异常。

假定toMap每次接收到的隐式参数(对象)为p(Person类型)
existingValue接收get(p.getId)(其中get是Map下方法), newValue接收Function.identity()
如果newValue为空,就将这个key删除。
如果newValue不为空,就将这个key对应的value赋为newValue
其间如果出现为同一个key赋多次值的情况,就返回IllegalStateException异常。

接下来从源码分析这个问题:
首先看toMap源码:

public static extends Map>
    Collector toMap(Functionsuper T, ? extends K> keyMapper,
                             Functionsuper T, ? extends U> valueMapper,
                             BinaryOperator mergeFunction,
                             Supplier mapFactory) {
        BiConsumer accumulator
                = (map, element) -> map.merge(keyMapper.apply(element),
                                              valueMapper.apply(element), mergeFunction);
        return new CollectorImpl<>(mapFactory, accumulator, mapMerger(mergeFunction), CH_ID);
    }

我们可以看到四个参数的toMap方法内部调用了map.merge,并为其传入了toMap方法的“第一个方法参数返回的对象key对应的value”、“第二个方法参数返回的对象”,以及第三个参数。在这里也就是传入了existingValuenewValue。(其中existingValue是一个传入的Id所属于的Person对象,newValue是Function.identity()
然后查看map.merge方法:

default V merge(K key, V value,
            BiFunction remappingFunction) {
        Objects.requireNonNull(remappingFunction);
        Objects.requireNonNull(value);
        V oldValue = get(key);
        V newValue = (oldValue == null) ? value :
                   remappingFunction.apply(oldValue, value);
        if (newValue == null) {
            remove(key);
        } else {
            put(key, newValue);
        }
        return newValue;
    }

主要讲其中三段代码:

第一步:
V oldValue = get(key):获取Person::getId对应的Person对象。

第二步:

V newValue = (oldValue == null) ? value :
                   remappingFunction.apply(oldValue, value);

其中apply方法属于一个函数式接口:

@FunctionalInterface
public interface BiFunction {

    /**
     * Applies this function to the given arguments.
     *
     * @param t the first function argument
     * @param u the second function argument
     * @return the function result
     */
    R apply(T t, U u);

apply方法的方法体已被覆写为:

{
    throw new IllegalStateException();
    }

如果oldValue为空,就把merge方法里传入的第二个引参value传给newValue。如果oldValue不为空,就说明出现了key重复的情况,就抛出一个IllegalStateException()异常。

第三步:

if (newValue == null) {
            remove(key);
        } else {
            put(key, newValue);
        }

如果传入的valuenull,就把key删除。
如果不是null,就把新的键值对传入map


3rd PART:

      locales = Stream.of(Locale.getAvailableLocales());
      Map<String, Set<String>> countryLanguageSets = locales.collect(
         Collectors.toMap(
            Locale::getDisplayCountry,
            l -> Collections.singleton(l.getDisplayLanguage()),
            (a, b) -> { // union of a and b
               Set<String> union = new HashSet<>(a);
               union.addAll(b);
               return union;
            }));

Locale.getAvailableLocales()
返回所有已安装的语言环境的流。

l -> Collections.singleton(l.getDisplayLanguage())
为每种语言存储一个单例集。

然后看以下代码:

(a, b) -> { // union of a and b
               Set union = new HashSet<>(a);
               union.addAll(b);
               return union;
            }

这里(a,b)接收的是两个Set对象。
其中a是get(Locale::getDisplayCountry)的返回值,b是一个单例集(toMap的第二个参数)。
在这段代码里覆写BiFunction函数式接口下的apply方法,将a,b进行聚合操作,把两个集合合并且返回。


有关Set和Map请参考Java集合框架系列目录


3.重要API

本篇程序清单主要涉及的需要掌握的API就是toMap的三种重载方式(分别为2参,3参,4参),以及相对应的参数完全相同的toConcurrentMap方法。
这里就不列出了,具体请参照《java核心技术卷Ⅱ》P22-23。

如有谬误或不完善之处,恳请斧正。

你可能感兴趣的:(Java核心技术卷Ⅱ程序清单1-5)