需求概述
需求一
经常开发中会碰到,许多对象集合需要根据某几个字段进行去重
解决方案一
Lists.newArrayList(user1, user2, user3).stream().collect(Collectors.collectingAndThen(
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(o -> o.getId() + ";" + o.getUsername()))), ArrayList::new))
解决方案二
上面这种方式看起来并不优雅,建议使用
CollectorExt.distinct(...)方法,来进行去重处理
需求二
开发中会碰到,对象集合转map,但是
collectors.tomap如果value属性为空,会抛空指针异常,正常可以通过filter来过滤空数据
例子(会抛空指针)
User user1 = new User(1, null);
User user2 = new User(1, "1");
Lists.newArrayList(user1, user2).stream().collect(Collectors.toMap(User::getId, User::getUsername)); //空指针
Lists.newArrayList(user1, user2).stream().filter(v -> Objects.nonNull(v.getName())).collect(Collectors.toMap(User::getId, User::getUsername));
解决方案
使用
CollectorExt.toMap(...)来解决
工具类/以及测试方法在下面粘出来了,功能可以正常使用,但是一些泛型可以在进行优化
工具类
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
public class CollectorExt {
static final Set<Collector.Characteristics> CH_ID
= Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));
static final Set<Collector.Characteristics> CH_CONCURRENT_NOID
= Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.CONCURRENT,
Collector.Characteristics.UNORDERED));
private static <T> BinaryOperator<T> throwingMerger() {
return (u, v) -> {
throw new IllegalStateException(String.format("Duplicate key %s", u));
};
}
private static <K, V, M extends Map<K, V>>
BinaryOperator<M> mapMerger(BinaryOperator<V> mergeFunction) {
return (m1, m2) -> {
for (Map.Entry<K, V> e : m2.entrySet())
m1.merge(e.getKey(), e.getValue(), mergeFunction);
return m1;
};
}
public static <T, K, U>
Collector<T, ?, Map<K, U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}
public static <T, K, U>
Collector<T, ?, Map<K, U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction) {
return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}
public static <T, K, U, M extends Map<K, U>>
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapSupplier) {
BiConsumer<M, T> accumulator = (map, element) -> {
K key = keyMapper.apply(element);
U value = valueMapper.apply(element);
if (map.containsKey(key)) {
Objects.requireNonNull(mergeFunction);
U old = map.get(key);
value = mergeFunction.apply(old, value);
}
map.put(key, value);
};
return new CollectorExt.CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
}
/**
* 属性去重
*
* @param functions 功能
* @return {@code Collector<T, TreeSet<T>, List<T>>}
*/
@SafeVarargs
public static <T, R extends Comparable> Collector<T, TreeSet<T>, List<T>> distinct(Function<T, R>... functions) {
return distinct(ArrayList:: new, functions);
}
@SafeVarargs
public static <T, R extends Comparable> Collector<T, TreeSet<T>, List<T>> distinct(Function<TreeSet<T>, List<T>> finisher, Function<T, R>... functions) {
Objects.requireNonNull(functions);
Comparator<T> comparing = Comparator.comparing(functions[0]);
for (int i = 1; i < functions.length; i++) {
comparing = comparing.thenComparing(functions[i]);
}
final Comparator<T> finalComparing = comparing;
Supplier<TreeSet<T>> supplier = () -> new TreeSet<>(finalComparing);
return new CollectorExt.CollectorImpl<>(supplier, TreeSet::add, (r1, r2) -> {
r1.addAll(r2);
return r1;
}, finisher, CH_CONCURRENT_NOID);
}
/**
* 属性去重
*
* @param <T> the type of element to be compared
* @param <U> the type of the {@code Comparable} sort key
* @param comparator the function used to extract the {@link
* Comparable} sort key
* @return Collector<T, TreeSet < T>, List<T>>
* @throws NullPointerException if the argument is null
*/
public static <T, U extends Comparable<? super U>> Collector<T, TreeSet<T>, List<T>> distinct(Comparator<T> comparator) {
Objects.requireNonNull(comparator);
Supplier<TreeSet<T>> supplier = () -> new TreeSet<>(comparator);
return new CollectorImpl<>(supplier, TreeSet::add, (set1, set2) -> {
set1.addAll(set2);
return set1;
}, ArrayList::new, CH_CONCURRENT_NOID);
}
static class CollectorImpl<T, A, R> implements Collector<T, A, R> {
private final Supplier<A> supplier;
private final BiConsumer<A, T> accumulator;
private final BinaryOperator<A> combiner;
private final Function<A, R> finisher;
private final Set<Characteristics> characteristics;
CollectorImpl(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Function<A, R> finisher,
Set<Characteristics> characteristics) {
this.supplier = supplier;
this.accumulator = accumulator;
this.combiner = combiner;
this.finisher = finisher;
this.characteristics = characteristics;
}
CollectorImpl(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Set<Characteristics> characteristics) {
this(supplier, accumulator, combiner, castingIdentity(), characteristics);
}
private static <I, R> Function<I, R> castingIdentity() {
return i -> (R) i;
}
@Override
public BiConsumer<A, T> accumulator() {
return accumulator;
}
@Override
public Supplier<A> supplier() {
return supplier;
}
@Override
public BinaryOperator<A> combiner() {
return combiner;
}
@Override
public Function<A, R> finisher() {
return finisher;
}
@Override
public Set<Characteristics> characteristics() {
return characteristics;
}
}
}
测试方法
import com.google.common.collect.Lists;
import com.innodealing.onshore.areaservice.function.BinaryOperatorExt;
import com.innodealing.onshore.areaservice.util.CollectorExt;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
public class CollectorExtTest {
@Test
public void testToMap() {
User user1 = new User(1L, "lhs");
User user2 = new User(1L, null);
User user3 = new User(null, "lhs1");
Map<Long, String> collect = Lists.newArrayList(user1, user2, user3).stream().collect(CollectorExt.toMap(User::getId, User::getName, BinaryOperatorExt.coverBehind()));
Assertions.assertEquals(collect.get(1L), "lhs");
Assertions.assertEquals(collect.get(null), "lhs1");
}
@Test
public void testDistinct() {
User user1 = new User(1L, "lhs");
User user2 = new User(1L, null);
List<User> collect1 = Lists.newArrayList(user1, user2).stream().collect(CollectorExt.distinct(User::getId));
Assertions.assertEquals(collect1.size(), 1);
Assertions.assertEquals(collect1.get(0).getName(), "lhs");
List<User> collect2 = Lists.newArrayList(user1, user2).stream().collect(CollectorExt.distinct(User::getId, User::getName));
Assertions.assertEquals(collect2.size(), 2);
Assertions.assertEquals(collect2.get(0).getName(), "lhs");
Assertions.assertNull(collect2.get(1).getName());
List<User> collect3 = Lists.newArrayList(user1, user2).stream().collect(CollectorExt.distinct(Comparator.comparing(User::getId)
.thenComparing(User::getName, Comparator.nullsLast(String::compareTo))));
Assertions.assertEquals(collect3.size(), 2);
Assertions.assertEquals(collect3.get(0).getName(), "lhs");
Assertions.assertNull(collect3.get(1).getName());
}
}
class User {
private Long id;
private String name;
public User(Long id, String name) {
this.id = id;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}