java:创建指定容器类型(如ImmutableSet)的Collector对象
之前对java 8的stream的理解只停留于基本的使用,比如将一个stream转为set,就直接用Collectors.toSet()
:
String[] array = new String[]{"hello","world"};
Stream.of(array).collect(Collectors.toSet());
这里,Collectors.toSet()提供的Collector实例将stream转为HashSet,
如果只是希望改变输出的集合类型,比如TreeSet,那也不复杂,用Collectors.toCollection
方法就能实现:
String[] array = new String[]{"hello","world"};
Stream.of(array).collect(Collectors.toCollection(TreeSet<String>::new));
但如果要转为不可变集合呢?
最近需要通过流创建指定类型的Map,Set,List,才对java.util.stream.Collector
有了更多的了解:
java.util.stream.Collector
提供了两个参数不同的of静态方法,用于创建Collector接口实例,
其中有finisher参数的方法可以帮助我解决这个问题:
/**** 类型参数:* <T> 新收集器的输入元素类型* <A>新型收集器的中间类型* <R> 新收集器的最终结果类型* 参数:* supplier 新收集器的供应商功能* accumulator 新收集器的累加器函数* combiner 新收集器的合并器函数* finisher 新收集器的终结器函数* characteristics 新收集器的收集器特性*/public static<T, A, R> Collector<T, A, R> of(Supplier<A> supplier,BiConsumer<A, T> accumulator,BinaryOperator<A> combiner,Function<A, R> finisher,Characteristics... characteristics) {}
示例1:不可变的LinkedHashSet
我们将一个Set转为不可变集合可以用java.util.Collections.unmodifiableSet
方法,但如何指定呢?了解上面这个方法用作用,就可以如下实现
/** 创建一个Collector实例,将stream中的元素转为LinkedHashSet,并包装为不可变对象 */
Collector.of(LinkedHashSet::new, // supplierSet::add, // accumulator(left, right) -> { left.addAll(right); return left; }, // combinera->(Set<T>)Collections.unmodifiableSet(a) // Collections.unmodifiableSet方法作finisher);
这里关键就是用到了finisher参数,通过这个参数将最后的LinkedHashSet对象通过Collections.unmodifiableSet方法封装为不可变对象
示例2:ImmutableSet
guava库的ImmutableSet对象的创建过程一般如下,先创建一个Builder,将元素添加到Builder,最后再用Builder对象生成ImmutableSet对象:
ImmutableSet.<String>Builder builder = ImmutableSet.builder();
builder.add("hello").add("world");
ImmutableSet<String> set = builder.build();
是否也能用Collector.of方法创建ImmutableSet对象呢?
前面这个Collector.of方法中有三个类型参数T, A, R
,其中A中间类型,R才是最终结果类型,那么当下这个场合,我们将ImmutableSet.Builder理解为中间结果类型,就知道Collector.of方法该怎么用了:
Collector.of(ImmutableSet.Builder<T>::new, // supplier 提供ImmutableSet.Builder对象作为中间结果对象ImmutableSet.Builder::add, // accumulator 在中间结果对象上实现累加(left, right) -> left.addAll(right.build()), // combinerImmutableSet.Builder::build // 使用ImmutableSet.Builder.build()方法当作finisher);
示例3:Map
有了上面的经验,创建Map也是差不多的方式,如下方法返回一个Collector实例,将stream中元素转为不可修改的LinkedHashMap:
public static <T, K, U> Collector<T, ?, Map<K,U>> toLinkedHashMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper) {return Collector.of(LinkedHashMap<K,U>::new, // supplier(map, element) -> map.put( // accumulatorkeyMapper.apply(element),valueMapper.apply(element)),(left, right) -> {left.putAll(right); return left;}, // combinera->(Map<K,U>)Collections.unmodifiableMap(a) // Collections.unmodifiableMap方法作finisher);
}
CollectorSupport
如此,可以实现一个通用工具类CollectorSupport ,提供常用容器类型的Collector实例
CollectorSupport .java
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;/*** 收集器支持类,提供一系列便捷方法用于创建不同类型的收集器<p>* 可用于将流元素收集到各种集合或映射中,包括不可变集合、有序集合和排序集合等。* @author guyadong* @since 2.1.0*/
public class CollectorSupport {/*** 创建一个用于将流元素收集到{@link ImmutableList}的收集器的便利方法<p>* 该收集器会将流中的元素添加到{@link ImmutableList.Builder}中,最终构建为不可变的{@link ImmutableList}。* * @param <T> 流中元素的类型* @return 一个新的收集器实例,可将流元素收集到{@link ImmutableList}中*/public static <T> Collector<T, ?, ImmutableList<T>> toImmutableList() {return Collector.of(ImmutableList.Builder<T>::new, // supplierImmutableList.Builder::add, // accumulator(left, right) -> left.addAll(right.build()), // combinerImmutableList.Builder::build // finisher);}/*** 创建一个用于将流元素收集到{@link ImmutableSet}的收集器的便利方法<p>* 该收集器会将流中的元素添加到{@link ImmutableSet.Builder}中,最终构建为不可变的{@link ImmutableSet}集合。* * @param <T> 流中元素的类型* @return 一个新的收集器实例,可将流元素收集到{@link ImmutableSet}中*/public static <T> Collector<T, ?, ImmutableSet<T>> toImmutableSet() {return Collector.of(ImmutableSet.Builder<T>::new, // supplierImmutableSet.Builder::add, // accumulator(left, right) -> left.addAll(right.build()), // combinerImmutableSet.Builder::build // finisher);}/*** 创建一个用于将流元素收集到{@link ImmutableMap}的收集器的便利方法<p>* 该收集器使用指定的键映射函数和值映射函数将流元素转换为键值对,<br>* 最终返回的映射为不可修改的{@link ImmutableMap}类型。* * @param <T> 流中元素的类型* @param <K> 映射键的类型* @param <V> 映射值的类型* @param keyMapper 用于从流元素中提取键的映射函数* @param valueMapper 用于从流元素中提取值的映射函数* @return 一个新的收集器实例,可将流元素收集到{@link ImmutableMap}中*/public static <T, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends V> valueMapper) {return Collector.of(ImmutableMap.Builder<K, V>::new, // supplier(builder, element) -> builder.put( // accumulatorkeyMapper.apply(element),valueMapper.apply(element)),(left, right) -> left.putAll(right.build()), // combinerImmutableMap.Builder::build // finisher);}/*** 创建一个用于将流元素收集到{@link LinkedHashSet}的收集器的便利方法<p>* 该收集器会保持元素插入的顺序,返回的集合为{@link LinkedHashSet}类型。<br>* 可通过参数指定返回的集合是否为不可修改的{@link Set}视图。* * @param <T> 流中元素的类型* @param unmodifiable 如果为{@code true},则返回的集合为不可修改的{@link Set}视图* @return 一个新的收集器实例,可将流元素收集到{@link LinkedHashSet}中*/@SuppressWarnings("unchecked")public static <T> Collector<T, ?, Set<T>> toLinkedHashSet(boolean unmodifiable) {if(unmodifiable) {return Collector.of(LinkedHashSet::new, // supplierSet::add, // accumulator(left, right) -> { left.addAll(right); return left; }, // combinera->(Set<T>)Collections.unmodifiableSet(a) // finisher); }return Collectors.toCollection(LinkedHashSet::new);}/*** 创建一个用于将流元素收集到{@link TreeSet}的收集器的便利方法<p>* 该收集器会根据元素的自然顺序对元素进行排序,若遇到重复元素会自动去重,<br>* 最终返回的集合类型为{@link TreeSet}。<br>* 可通过{@code unmodifiable}参数指定返回的集合是否为不可修改的视图。* * @param <T> 流中元素的类型* @param unmodifiable 如果为{@code true},则返回的集合为不可修改的{@link Set}视图* @return 一个新的收集器实例,可将流元素收集到{@link TreeSet}中*/@SuppressWarnings("unchecked")public static <T> Collector<T, ?, Set<T>> toTreeSet(boolean unmodifiable) {if(unmodifiable) {return Collector.of(TreeSet::new, // supplierSet::add, // accumulator(left, right) -> { left.addAll(right); return left; }, // combinera->(Set<T>)Collections.unmodifiableSet(a) // finisher);}return Collectors.toCollection(TreeSet::new);}/**/*** 创建一个用于将流元素收集到{@link LinkedHashMap}的收集器的便利方法<p>* 该收集器会保持元素插入的顺序,使用指定的键映射函数和值映射函数将流元素转换为键值对,<br>* 若遇到重复键,后面的值会覆盖前面的值,最终返回的映射为{@link LinkedHashMap}类型。* * @param <T> 流中元素的类型* @param <K> 映射键的类型* @param <U> 映射值的类型* @param keyMapper 用于从流元素中提取键的映射函数* @param valueMapper 用于从流元素中提取值的映射函数* @param unmodifiable 如果为{@code true},则返回的映射为不可修改的{@link Map}视图* @return 一个新的收集器实例,可将流元素收集到{@link LinkedHashMap}中*/public static <T, K, U> Collector<T, ?, Map<K,U>> toLinkedHashMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper,boolean unmodifiable) {if(unmodifiable) {return Collector.of(LinkedHashMap<K,U>::new, // supplier(map, element) -> map.put( // accumulatorkeyMapper.apply(element),valueMapper.apply(element)),(left, right) -> {left.putAll(right); return left;}, // combinera->(Map<K,U>)Collections.unmodifiableMap(a) // finisher);}return Collectors.toMap(keyMapper, valueMapper, (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); }, LinkedHashMap::new);}/*** 将元素收集到一个{@link TreeMap}中。<br>* <p>使用指定的键映射函数和值映射函数将输入元素转换为键值对,添加到{@link TreeMap}中。</p>* <p>可以指定是否返回不可修改的映射。</p>* * @param <T> 输入元素的类型* @param <K> 映射键的类型* @param <U> 映射值的类型* @param keyMapper 用于从输入元素中提取键的映射函数* @param valueMapper 用于从输入元素中提取值的映射函数* @param unmodifiable 是否返回不可修改的映射,true表示返回不可修改的映射,false表示返回普通的{@link TreeMap}* @return 一个{@link Collector}实例,用于将输入元素收集到{@link TreeMap}中*/public static <T, K, U> Collector<T, ?, Map<K,U>> toTreeMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper,boolean unmodifiable) {if(unmodifiable) {return Collector.of(TreeMap<K,U>::new, // supplier(map, element) -> map.put( // accumulatorkeyMapper.apply(element),valueMapper.apply(element)),(left, right) -> {left.putAll(right); return left;}, // combinera->(Map<K,U>)Collections.unmodifiableMap(a) // finisher);}return Collectors.toMap(keyMapper, valueMapper, (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); }, TreeMap::new);}
}