java 8 将 ListB 的所有元素合并到 ListA 中(如果不存在)

2022-09-04 22:06:22

我需要将 listB 的所有元素合并到另一个 list listA 中

如果 listA 中已经存在一个元素(基于自定义相等检查),我不想添加它。

我不想使用 Set,也不想覆盖 equals() 和 hashCode()。

原因是,我不想阻止listA本身的重复,我只想不从listB合并,如果listA中已经存在我认为相等的元素。

我不想覆盖 equals() 和 hashCode(),因为这意味着我需要确保,我对元素的 equals() 实现在任何情况下都成立。但是,列表 B 中的元素可能未完全初始化,即它们可能缺少对象 ID,而该对象 ID 可能存在于 listA 的元素中。

我目前的方法涉及一个接口和一个实用程序函数:

public interface HasEqualityFunction<T> {

    public boolean hasEqualData(T other);
}

public class AppleVariety implements HasEqualityFunction<AppleVariety> {
    private String manufacturerName;
    private String varietyName;

    @Override
    public boolean hasEqualData(AppleVariety other) {
        return (this.manufacturerName.equals(other.getManufacturerName())
            && this.varietyName.equals(other.getVarietyName()));
    }

    // ... getter-Methods here
}


public class CollectionUtils {
    public static <T extends HasEqualityFunction> void merge(
        List<T> listA,
        List<T> listB) {
        if (listB.isEmpty()) {
            return;
        }
        Predicate<T> exists
            = (T x) -> {
                return listA.stream().noneMatch(
                        x::hasEqualData);
            };
        listA.addAll(listB.stream()
            .filter(exists)
            .collect(Collectors.toList())
        );
    }
}

然后我会这样使用它:

...
List<AppleVariety> appleVarietiesFromOnePlace = ... init here with some elements
List<AppleVariety> appleVarietiesFromAnotherPlace = ... init here with some elements
CollectionUtils.merge(appleVarietiesFromOnePlace, appleVarietiesFromAnotherPlace);
...

在 listA 中获取我的新列表,其中所有元素都从 B 合并。

这是一个好方法吗?有没有更好/更简单的方法来完成同样的事情?


答案 1

你想要这样的东西:

public static <T> void merge(List<T> listA, List<T> listB, BiPredicate<T, T> areEqual) {
    listA.addAll(listB.stream()
                      .filter(t -> listA.stream().noneMatch(u -> areEqual.test(t, u)))
                      .collect(Collectors.toList())
    );
}

您不需要接口。您可以重复使用来测试这两个对象在逻辑方面是否相等。HasEqualityFunctionBiPredicate

此代码仅根据给定的谓词筛选不包含的元素。它确实遍历了 与 中的元素一样多的遍历次数。listBlistAlistAlistB


另一种性能更好的实现是使用包装器类来包装元素,并将谓词作为方法:equals

public static <T> void merge(List<T> listA, List<T> listB, BiPredicate<T, T> areEqual, ToIntFunction<T> hashFunction) {

    class Wrapper {
        final T wrapped;
        Wrapper(T wrapped) {
            this.wrapped = wrapped;
        }
        @Override
        public boolean equals(Object obj) {
            return areEqual.test(wrapped, ((Wrapper) obj).wrapped);
        }
        @Override
        public int hashCode() {
            return hashFunction.applyAsInt(wrapped);
        }
    }

    Set<Wrapper> wrapSet = listA.stream().map(Wrapper::new).collect(Collectors.toSet());

    listA.addAll(listB.stream()
                      .filter(t -> !wrapSet.contains(new Wrapper(t)))
                      .collect(Collectors.toList())
    );
}

这首先将每个元素包装在对象内,并将它们收集到一个 .然后,它筛选此集中未包含的元素。相等性检验是通过委托给给定的谓词来完成的。约束是,我们还需要给出一个来正确实现。WrapperSetlistBhashFunctionhashCode

示例代码为:

List<String> listA = new ArrayList<>(Arrays.asList("foo", "bar", "test"));
List<String> listB = new ArrayList<>(Arrays.asList("toto", "foobar"));
CollectionUtils.merge(listA, listB, (s1, s2) -> s1.length() == s2.length(), String::length);
System.out.println(listA);

答案 2

您可以使用基于 Eclipse Collections 的 HashingStrategySet

如果可以使用可变列表接口:

public static void merge(MutableList<AppleVariety> listA, MutableList<AppleVariety> listB)
{
    MutableSet<AppleVariety> hashingStrategySet = HashingStrategySets.mutable.withAll(
        HashingStrategies.fromFunctions(AppleVariety::getManufacturerName,
            AppleVariety::getVarietyName), 
        listA);   
    listA.addAllIterable(listB.asLazy().reject(hashingStrategySet::contains));
}

如果无法从 以下位置更改列表 A 和列表 B 的类型:List

public static void merge(List<AppleVariety> listA, List<AppleVariety> listB)
{
    MutableSet<AppleVariety> hashingStrategySet = HashingStrategySets.mutable.withAll(
        HashingStrategies.fromFunctions(AppleVariety::getManufacturerName,
            AppleVariety::getVarietyName), 
        listA);
    listA.addAll(ListAdapter.adapt(listB).reject(hashingStrategySet::contains));
}

注意:我是 Eclipse Collections 的贡献者。