【问题标题】:java 8 merge all elements of ListB into ListA if not presentjava 8如果不存在则将List的所有元素合并到List中
【发布时间】:2016-07-11 21:58:55
【问题描述】:

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

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

我不想使用 Set,也不想重写 equals() 和 hashCode()。

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

我不想覆盖 equals() 和 hashCode(),因为这意味着我需要确保我对元素的 equals() 实现在任何情况下都适用。然而,listB 中的元素可能没有完全初始化,即它们可能会丢失一个对象 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 合并。

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

【问题讨论】:

    标签: java collections java-8 java-stream


    【解决方案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())
        );
    }
    

    您不需要HasEqualityFunction 接口。您可以重用BiPredicate 来测试两个对象在您的逻辑方面是否相等。

    此代码根据给定的谓词仅过滤listB 中不包含在listA 中的元素。它遍历listA 的次数与listB 中的元素一样多。


    另一种性能更好的实现是使用一个包装类来包装你的元素,并将你的谓词作为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())
        );
    }
    

    这首先将每个元素包装在Wrapper 对象中,并将它们收集到Set 中。然后,它过滤不包含在此集合中的listB 的元素。相等性测试是通过委托给给定的谓词来完成的。约束是我们还需要提供hashFunction 才能正确实现hashCode

    示例代码如下:

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

    【讨论】:

    • 谢谢,我已经将您的建议应用于 BiPredicate 并消除了接口。稍后我还将研究您的第二个建议 - 使用包装类能够在特定上下文中覆盖 equals 和 hashCode 是一个好主意。
    【解决方案2】:

    您可以使用基于SetHashingStrategy Eclipse Collections

    如果可以使用MutableList接口:

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

    如果不能从List更改listA和listB的类型:

    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 的贡献者。

    【讨论】:

      猜你喜欢
      • 2020-05-17
      • 1970-01-01
      • 2017-02-14
      • 2021-06-08
      • 1970-01-01
      • 2012-04-27
      • 2019-07-29
      • 1970-01-01
      相关资源
      最近更新 更多