对象集合根据多属性字段进行去重,以及解决Collectors.toMap空指针异常

136 阅读2分钟

需求概述

需求一

经常开发中会碰到,许多对象集合需要根据某几个字段进行去重

解决方案一

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;
    }
}