P21-22
程序清单1-5 collecting/CollectingIntoMaps.java
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);
}
}
我们直接进入main函数:
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.
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
:传入一个构造器TreeMap::new
,从而将收集得到的键值对转换为TreeMap
返回。
而有关Collectors.toMap
的第三个引参BiFunction super V, ? super V, ? extends V> 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(Function super T, ? extends K> keyMapper,
Function super 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”、“第二个方法参数返回的对象”,以及第三个参数。在这里也就是传入了existingValue
,newValue
。(其中existingValue是一个传入的Id所属于的Person对象,newValue是Function.identity()
)
然后查看map.merge
方法:
default V merge(K key, V value,
BiFunction super V, ? super V, ? extends V> 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);
}
如果传入的value
是null
,就把key
删除。
如果不是null
,就把新的键值对传入map
。
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集合框架系列目录
本篇程序清单主要涉及的需要掌握的API就是toMap的三种重载方式(分别为2参,3参,4参),以及相对应的参数完全相同的toConcurrentMap方法。
这里就不列出了,具体请参照《java核心技术卷Ⅱ》P22-23。
如有谬误或不完善之处,恳请斧正。