如何比较两个集合"等价"基于来自不同Java类的字段?

时间:2016-11-21 10:12:33

标签: java collections guava equality apache-commons

鉴于任何两个类,例如下面有ClassAClassB

class ClassA {
    private int intA;
    private String strA;
    private boolean boolA;
    // Constructor
    public ClassA (int intA, String strA, boolean boolA) {
        this.intA = intA; this.strA = strA; this.boolA = boolA;
    } // Getters and setters etc. below...
}

class ClassB {
    private int intB;
    private String strB;
    private boolean boolB;
    // Constructor
    public ClassB (int intB, String strB, boolean boolB) {
        this.intB = intB; this.strB = strB; this.boolB = boolB;
    } // Getters and setters etc. below...
}

以及任何两种不同的Collection类型,一种具有ClassA元素,另一种具有ClassB元素,例如:

List<Object> myList = Arrays.asList(new ClassA(1, "A", true),
                                    new ClassA(2, "B", true));
Set<Object> mySet = new HashSet<Object>(
                      Arrays.asList(new ClassB(1, "A", false),
                                    new ClassB(2, "B", false)));

根据指定的字段子集,判断两个Collection是否等同于&#34;(*)的最简单方法是什么?

(*)单词&#34;等同于&#34;使用而不是&#34;相等&#34;因为这是语境 - 即这样的&#34;等价&#34;可能在另一个背景下有不同的定义。

上面的示例: 假设我们指定intAstrA分别与intBstrB匹配(但boolA / boolB值可以忽略)。这将使上面定义的两个集合对象被认为是等价的 - 但是如果一个元素被添加到其中一个集合或从其中删除,那么它们将不再是。

首选解决方案: 对于任何Collection类型,使用的方法都应该是通用的。理想情况下Java 7仅限于使用它(但Java 8可能对其他人有额外的兴趣)。很高兴使用Guava或Apache Commons,但不想使用更加模糊的外部库。

13 个答案:

答案 0 :(得分:8)

这是使用lambdas和高阶函数的Java 8版本。可能使用匿名内部类而不是lambdas将其转换为Java 7。 (我相信大多数IDE都有重构操作来执行此操作。)我将此作为练习留给感兴趣的读者。

这里实际上有两个不同的问题:

  1. 给定两个不同类型的对象,通过检查每个对象的相应字段来评估它们。这与&#34; equals&#34;不同。并且&#34;比较&#34;已经由JDK库API定义的操作,因此我将使用术语&#34;等效的&#34;代替。

  2. 鉴于两个集合包含这些类型的元素,请确定它们是否为&#34;等于&#34;对于该术语的某些定义。这实际上非常微妙;见下面的讨论。

  3. <强> 1。等价

    鉴于两个类型TU的对象,我们想确定它们是否相同。结果是布尔值。这可以由类型BiPredicate<T,U>的函数表示。但我们不能直接检查对象;相反,我们需要从每个对象中提取相应的字段,并相互评估提取结果。如果从T提取的字段属于TR类型,而从U提取的字段属于UR类型,则提取器由函数类型表示

    Function<T, TR>
    Function<U, UR>
    

    现在我们提取了TRUR类型的结果。我们可以在它们上面调用equals(),但这会造成不必要的限制。相反,我们可以提供另一个等价函数,它将被调用以相互评估这两个结果。那是BiPredicate<TR,UR>

    鉴于这一切,我们可以编写一个更高阶的函数,它接受所有这些函数并为我们生成和等价函数(包含完整性的通配符):

    static <T,U,TR,UR> BiPredicate<T,U> equiv(Function<? super T, TR> tf,
                                              Function<? super U, UR> uf,
                                              BiPredicate<? super TR, ? super UR> pred) {
        return (t, u) -> pred.test(tf.apply(t), uf.apply(u));
    }
    

    使用equals()评估字段提取结果可能是一种常见情况,因此我们可以为此提供重载:

    static <T,U> BiPredicate<T,U> equiv(Function<? super T, ?> tf,
                                        Function<? super U, ?> uf) {
        return (t, u) -> equiv(tf, uf, Object::equals).test(t, u);
    }
    

    我本可以提供另一个类型变量R作为两个函数的结果类型,以确保它们属于同一类型,但事实证明这并非必要。由于在equals()上定义了Object并且它需要Object个参数,因此我们实际上并不关心函数返回类型是什么,因此也就是通配符。

    以下是如何使用它来仅使用字符串字段来评估OP的示例类:

    ClassA a = ... ;
    ClassB b = ... ;
    if (equiv(ClassA::getStrA, ClassB::getStrB).test(a, b)) {
        // they're equivalent
    }
    

    作为一种变体,我们可能还需要原始的专业化以避免不必要的装箱:

    static <T,U> BiPredicate<T,U> equivInt(ToIntFunction<? super T> tf,
                                           ToIntFunction<? super U> uf) {
        return (t, u) -> tf.applyAsInt(t) == uf.applyAsInt(u);
    }
    

    这使我们可以基于单个字段构造等价函数。如果我们想基于多个字段评估等价,该怎么办?我们可以通过链接and()方法组合任意数量的BiPredicates。以下是如何使用OP示例中的类的intString字段创建评估等效性的函数。为此,最好将函数存储在一个变量中,而不是使用它,尽管这可能都是内联的(我认为这会让它变得不可读):

    BiPredicate<ClassA, ClassB> abEquiv =
        equivInt(ClassA::getIntA, ClassB::getIntB)
            .and(equiv(ClassA::getStrA, ClassB::getStrB));
    
    if (abEquiv.test(a, b)) {
        // they're equivalent
    }
    

    作为最后一个例子,在为两个类创建等价函数时,能够为字段提取结果提供等价函数是非常强大的。例如,假设我们想要提取两个String字段,并且如果提取的字符串是等于的,则认为它们是等价的,忽略大小写。以下代码会生成true

    equiv(ClassA::getStrA, ClassB::getStrB, String::equalsIgnoreCase)
        .test(new ClassA(2, "foo", true),
              new ClassB(3, "FOO", false))
    

    <强> 2。收集“平等”

    第二部分是评估两个集合是否等于&#34;等于&#34;在某种意义上。问题是在集合框架中,定义了相等的概念,使得List只能等于另一个List,而Set只能等于另一个Set。因此,其他类型的Collection永远不能等于List或Set。有关这一点的一些讨论,请参阅Collection.equals()的规范。

    这显然与OP想要的不一样。正如OP所建议的那样,我们并不真正想要&#34;平等,&#34;但是我们想要一些我们需要提供定义的其他属性。根据OP的例子以及Przemek Gumulajanos在其他答案中的一些建议,似乎我们希望两个集合中的元素以某种方式一一对应。我将此称为bijection,这可能不是数学上的精确,但它似乎足够接近。此外,每对元素之间的对应关系应该是等价,如上所述。

    计算这个有点微妙,因为我们有自己的等价关系。我们无法使用许多内置集合操作,因为它们都使用equals()。我的第一次尝试是这样的:

    // INCORRECT
    static <T,U> boolean isBijection(Collection<T> c1,
                                     Collection<U> c2,
                                     BiPredicate<? super T, ? super U> pred) {
        return c1.size() == c2.size() &&
               c1.stream().allMatch(t -> c2.stream()
                                           .anyMatch(u -> pred.test(t, u)));
    }
    

    (这与Przemek Gumula给出的基本相同。)这有问题,可归结为一个集合中多个元素对应于另一个集合中的单个元素的可能性,留下元素无法比拟的。如果给定两个多重集,使用相等作为等价函数,则会产生奇怪的结果:

    {a x 2, b}    // essentially {a, a, b}
    {a, b x 2}    // essentially {a, b, b}
    

    这个函数认为这两个多重集合是一个双射,但事实并非如此。如果等价函数允许多对一匹配,则会出现另一个问题:

    Set<String> set1 = new HashSet<>(Arrays.asList("foo", "FOO", "bar"));
    Set<String> set2 = new HashSet<>(Arrays.asList("fOo", "bar", "quux"));
    
    isBijection(set1, set2, equiv(s -> s, s -> s, String::equalsIgnoreCase))
    

    结果为true,但如果以相反的顺序给出集合,则结果为false。这显然是错的。

    另一种算法是创建临时结构并删除匹配的元素。结构必须考虑重复,因此我们需要递减计数,并且只在计数达到零时才删除元素。幸运的是,各种Java 8功能使这非常简单。这与janos的答案中使用的算法非常相似,尽管我已经将等价函数提取到方法参数中。唉,因为我的等价函数可以有嵌套的等价函数,这意味着我无法探测映射(由相等定义)。相反,我必须搜索地图的键,这意味着算法是O(N ^ 2)。哦,好吧。

    然而,代码非常简单。首先,使用groupingBy从第二个集合生成频率图。然后,迭代第一个集合的元素,并搜索频率映射的键以获得等价物。如果找到一个,则其出现次数递减。请注意传递给Map.compute()的重映射函数的null的返回值。这具有删除条目的副作用,而不是将映射设置为null。它有点像API黑客,但它非常有效。

    对于第一个集合中的每个元素,必须找到第二个集合中的等效元素,否则它将失效。在处理完第一个集合的所有元素之后,频率图中的所有元素也应该被处理,因此它只是被测试为空。

    以下是代码:

    static <T,U> boolean isBijection(Collection<T> c1,
                                     Collection<U> c2,
                                     BiPredicate<? super T, ? super U> pred) {
        Map<U, Long> freq = c2.stream()
                              .collect(Collectors.groupingBy(u -> u, Collectors.counting()));
        for (T t : c1) {
            Optional<U> ou = freq.keySet()
                                 .stream()
                                 .filter(u -> pred.test(t, u))
                                 .findAny();
            if (ou.isPresent()) {
                freq.compute(ou.get(), (u, c) -> c == 1L ? null : c - 1L);
            } else {
                return false;
            }
        }
    
        return freq.isEmpty();
    }
    

    这个定义是否正确并不完全清楚。但直觉上似乎是人们想要的东西。但它很脆弱。如果等价函数不对称,isBijection将失败。也有一些自由度并没有被考虑在内。例如,假设集合是

    {a, b}
    {x, y}
    

    a相当于xy,但b仅相当于x。如果ax匹配,则isBijection的结果为false。但如果ay匹配,则结果为true

    将它放在一起

    这是OP的示例,使用equiv()equivInt()isBijection函数进行编码:

    List<ClassA> myList = Arrays.asList(new ClassA(1, "A", true),
                                        new ClassA(2, "B", true));
    
    Set<ClassB> mySet = new HashSet<>(Arrays.asList(new ClassB(1, "A", false),
                                                    new ClassB(2, "B", false)));
    
    BiPredicate<ClassA, ClassB> abEquiv =
        equivInt(ClassA::getIntA, ClassB::getIntB)
            .and(equiv(ClassA::getStrA, ClassB::getStrB));
    
    isBijection(myList, mySet, abEquiv)
    

    结果是true

答案 1 :(得分:7)

另一种可能的解决方案是使用谓词编写一个简单的比较方法(因此您可以明确指定两个类的条件在您的术语上相似)。我在Java 8中创建了这个:

<T, U> boolean compareCollections(Collection<T> coll1, Collection<U> coll2, BiPredicate<T, U> predicate) {

    return coll1.size() == coll2.size()
            && coll1.stream().allMatch(
            coll1Item -> coll2.stream().anyMatch(col2Item -> predicate.test(coll1Item, col2Item))
    );
}

如您所见,它会比较大小,然后检查集合中的每个元素是否在第二个集合中都有对应物(虽然它不是比较顺序)。它是在Java 8中,但您可以通过实现一个简单的BiPredicate代码将其移植到Java 7,allMatch和anyMatch(每个代码都应该足够用于循环)

编辑:Java 7代码:

<T, U> boolean compareCollections(Collection<T> coll1, Collection<U> coll2, BiPredicate<T, U> predicate) {

        if (coll1.size() != coll2.size()) {
            return false;
        }

        for (T item1 : coll1) {
            boolean matched = false;
            for (U item2 : coll2) {
                if (predicate.test(item1, item2)) {
                    matched = true;
                }
            }

            if (!matched) {
                return false;
            }
        }
        return true;

    }}
interface BiPredicate <T, U> {
    boolean test(T t, U u);
}

这是usage example

答案 2 :(得分:3)

没有非常简单的方法。

与常规Java集合一起使用的最通用的是创建一个包装类,它将ClassAClassB作为输入,然后覆盖由您定义的equals / hashcode。 / p>

在某些情况下,您可以滥用Comparator,但这会将您限制为TreeMap/TreeSet。 您还可以实现equals()方法,以便classA.equals(classB);返回true,但如果您不小心,这可能会导致棘手的错误。它还会导致a.equals(b)b.equals(c)!a.equals(c)的有趣情况。

某些库(Guava?)还有一个Comparator样式的机制进行相等性测试,但这只适用于库的集合。

答案 3 :(得分:2)

Apache Commons Lang有EqualsBuilder#reflectionEquals(Object, Object)

  

此方法使用反射来确定两个Object是否相等。

     

它使用AccessibleObject.setAccessible来访问私有   领域。这意味着如果运行它将抛出安全异常   在安全管理器下,如果未正确设置权限。   它也没有明确测试那么高效。非原始   使用equals()比较字段。

     

瞬态成员将不会被测试,因为它们可能是派生的   字段,而不是Object的值的一部分。

     

不会测试静态字段。将包括超类字段。

所以这应该涵盖你的用例。明显的免责声明:它使用反射;)

编辑:当然,这假设字段具有相同的名称,而不是类型。在后一种情况下,可以检查source code并将其调整为用例。

答案 4 :(得分:2)

两个现有答案的组合:Kayaman建议的封装类的通用版本(Just a List)。使用ArrayList :: equals作为Przemek Gumula方法的谓词。

我添加了一个Builder,使其使用起来更好:

    StructureEqual<ClassA, ClassB> struct = StructureEqual.<ClassA, ClassB>builder()
            .field(ClassA::getIntA, ClassB::getIntB) // Declare what fields should be checked
            .field(ClassA::getStrA, ClassB::getStrB)
            .build();

    System.out.println(struct.isEqual(myList, mySet));

实际代码:

public class StructureEqual<A, B> {
    private List<EqualPoint<A, B>> points;

    public StructureEqual(List<EqualPoint<A, B>> points) {
        this.points = points;
    }

    private List<Object> sampleA(A a) {
        return points.stream().map(p -> p.getAPoint().apply(a)).collect(Collectors.toList());
    }

    private List<Object> sampleB(B b) {
        return points.stream().map(p -> p.getBPoint().apply(b)).collect(Collectors.toList());
    }

    public boolean isEqual(Collection<A> as, Collection<B> bs) {
        Set<List<Object>> aSamples = as.stream().map(this::sampleA).collect(Collectors.toSet());
        Set<List<Object>> bSamples = bs.stream().map(this::sampleB).collect(Collectors.toSet());
        return aSamples.equals(bSamples);
    }

    private static class EqualPoint<PA, PB> {

        private final Function<PA, ?> aPoint;
        private final Function<PB, ?> bPoint;

        public <T> EqualPoint(Function<PA, T> aPoint, Function<PB, T> bPoint) {
            this.aPoint = aPoint;
            this.bPoint = bPoint;
        }

        Function<PA, ?> getAPoint() {
            return aPoint;
        }

        Function<PB, ?> getBPoint() {
            return bPoint;
        }
    }

    public static <BA, BB> Builder<BA, BB> builder() {
        return new Builder<>();
    }

    public static class Builder<BA, BB> {
        private List<EqualPoint<BA, BB>> points = new ArrayList<>();

        public <T> Builder<BA, BB> field(Function<BA, T> a, Function<BB, T> b) {
            points.add(new EqualPoint<>(a, b));
            return this;
        }

        public StructureEqual<BA, BB> build() {
            return new StructureEqual<>(Collections.unmodifiableList(points));
        }
    }

}

答案 5 :(得分:2)

  

根据指定的字段子集判断两个Collections是否相等的最简单方法是什么?

根据您的描述,您的平等要求是:

  • 收藏品大小相同。
  • 对于item1中的每个collection1item2中存在collection2item1.field_x等于item2.field_y,对于多个定义{ {1}} - field_x对。

如果我们可以假设两个集合中都没有重复的元素, 也就是说,这是最简单的方式&#34;可能是这样的:

field_y

这是对要求的直接实施。 但是因为它可以将每个元素与其他集合中的每个其他元素进行比较, 它性能很差, public boolean areEqual(Collection<ClassA> c1, Collection<ClassB> c2) { if (c1.size() != c2.size()) { return false; } OUTER: for (ClassA a : c1) { for (ClassB b : c2) { if (a.getIntA() == b.getIntB() && Objects.equals(a.getStringA(), b.getStringB())) { continue OUTER; } } return false; } return true; } 其中O(n^2)是集合的大小。

如果相同的元素可以在集合中多次出现,这也可能无效:

n

此处 List<ClassA> list = new ArrayList<>(Arrays.asList( new ClassA(1, "A", true), new ClassA(1, "A", false), new ClassA(2, "B", true) )); Set<ClassB> set = new HashSet<>(Arrays.asList( new ClassB(1, "A", false), new ClassB(2, "B", false), new ClassB(2, "B", true) )); ClassA(1, "A", true)在第一个列表中被视为等效,ClassA(1, "A", false)new ClassB(2, "B", false)在第二个列表中被视为等效。 上面的算法会发现这两个集合相等,这是不正确的。

可以处理重复的情况,同时以牺牲额外空间为代价来提高时间复杂度:

  • 迭代第一个集合以构建计数new ClassB(2, "B", true)元组
  • 的映射
  • 在检查计数图时迭代第二个集合:
    • 如果地图的数量不包含相应的(int, String)元组,(int, String),因为这意味着元素没有匹配的对
    • 如果存在相应的元组,则减少其计数
    • 如果计数达到0,则来自地图中的元组
  • 如果到达循环结束,则必须表示所有项目都匹配(地图应为空),因此您只需return false

实现:

return true

一些断言测试来验证实现:

class FieldExtractingEqual {

    public boolean areEqual(Collection<ClassA> c1, Collection<ClassB> c2) {
        if (c1.size() != c2.size()) {
            return false;
        }

        Map<Tuple, Integer> counts = new HashMap<>();
        for (ClassA a : c1) {
            Tuple tuple = new Tuple(a.getIntA(), a.getStringA());
            Integer count = counts.get(tuple);
            if (count == null) {
                count = 0;
            }
            counts.put(tuple, count + 1);
        }

        for (ClassB b : c2) {
            Tuple tuple = new Tuple(b.getIntB(), b.getStringB());
            Integer count = counts.get(tuple);
            if (count == null) {
                return false;
            }
            if (count == 1) {
                counts.remove(tuple);
            } else {
                counts.put(tuple, count - 1);
            }
        }

        return true;
    }

    private static class Tuple {
        private final Object[] values;

        public Tuple(Object... values) {
            this.values = values;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            Tuple tuple = (Tuple) o;

            return Arrays.equals(values, tuple.values);
        }

        @Override
        public int hashCode() {
            return Arrays.hashCode(values);
        }
    }
}

作为进一步改进, 可以使List<ClassA> myList = new ArrayList<>(Arrays.asList( new ClassA(1, "A", true), new ClassA(1, "A", true), new ClassA(2, "B", true) )); Set<ClassB> mySet = new HashSet<>(Arrays.asList( new ClassB(1, "A", false), new ClassB(1, "A", true), new ClassB(2, "B", false) )); FieldExtractingEqual comp = new FieldExtractingEqual(); assertThat(comp.areEqual(myList, mySet)).isTrue(); myList.add(new ClassA(3, "X", true)); mySet.add(new ClassB(3, "Y", true)); assertThat(comp.areEqual(myList, mySet)).isFalse(); 泛型的实现, 这样它可以采用任意FieldExtractingEqualCollection<A>个参数, 和提取器对用于创建来自Collection<B>A的元组。 这是实现这一目标的一种方法:

B

示例用法和一些断言测试:

interface FieldExtractor<T, V> {
    V apply(T arg);
}

class GenericFieldExtractingEqual<T, U> {
    private final List<FieldExtractor<T, ?>> extractors1;
    private final List<FieldExtractor<U, ?>> extractors2;

    private GenericFieldExtractingEqual(List<FieldExtractor<T, ?>> extractors1, List<FieldExtractor<U, ?>> extractors2) {
        this.extractors1 = extractors1;
        this.extractors2 = extractors2;
    }

    public boolean areEqual(Collection<T> c1, Collection<U> c2) {
        if (c1.size() != c2.size()) {
            return false;
        }

        Map<Tuple, Integer> counts = new HashMap<>();
        for (T a : c1) {
            Tuple tuple = newTuple1(a);
            Integer count = counts.get(tuple);
            if (count == null) {
                count = 0;
            }
            counts.put(tuple, count + 1);
        }

        for (U b : c2) {
            Tuple tuple = newTuple2(b);
            Integer count = counts.get(tuple);
            if (count == null) {
                return false;
            }
            if (count == 1) {
                counts.remove(tuple);
            } else {
                counts.put(tuple, count - 1);
            }
        }

        return true;
    }

    private Tuple newTuple1(T a) {
        return new Tuple(extractors1.stream().map(x -> x.apply(a)).toArray());
    }

    private Tuple newTuple2(U b) {
        return new Tuple(extractors2.stream().map(x -> x.apply(b)).toArray());
    }

    private static class Tuple {
        private final Object[] values;

        public Tuple(Object... values) {
            this.values = values;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            Tuple tuple = (Tuple) o;

            return Arrays.equals(values, tuple.values);
        }

        @Override
        public int hashCode() {
            return Arrays.hashCode(values);
        }
    }

    public static class Builder<T, U> {
        List<FieldExtractor<T, ?>> extractors1 = new ArrayList<>();
        List<FieldExtractor<U, ?>> extractors2 = new ArrayList<>();

        <V> Builder<T, U> addExtractors(FieldExtractor<T, V> extractor1, FieldExtractor<U, V> extractor2) {
            extractors1.add(extractor1);
            extractors2.add(extractor2);
            return this;
        }

        GenericFieldExtractingEqual<T, U> build() {
            return new GenericFieldExtractingEqual<>(new ArrayList<>(extractors1), new ArrayList<>(extractors2));
        }
    }
}

也就是说,您从提取器对中构建GenericFieldExtractingEqual<ClassA, ClassB> comp2 = new GenericFieldExtractingEqual.Builder<ClassA, ClassB>() .addExtractors(ClassA::getIntA, ClassB::getIntB) .addExtractors(ClassA::getStringA, ClassB::getStringB) .build(); assertThat(comp2.areEqual(myList, mySet)).isTrue(); myList.add(new ClassA(3, "X", true)); mySet.add(new ClassB(3, "Y", true)); assertThat(comp2.areEqual(myList, mySet)).isFalse(); 实例,例如:

GenericFieldExtractingEqual

第一个参数是一个提取第一个类中的字段的对象, 第二个参数是一个提取第二个类中相应字段的对象。 您可以添加任意数量的提取器对,以便比较相等条件。

虽然我使用Java8写作风格 .addExtractors(ClassA::getIntA, ClassB::getIntB) 来实现紧凑性, 转换为ClassA::getIntA实现很容易(但很冗长):

FieldExtractor

.addExtractors( new FieldExtractor<ClassA, Integer>() { @Override public Integer apply(ClassA arg) { return arg.getIntA(); } }, new FieldExtractor<ClassB, Integer>() { @Override public Integer apply(ClassB arg) { return arg.getIntB(); } } ) 实用程序方法也是如此。

Here's a runnable version on RexTester.

答案 6 :(得分:1)

以下是我的回答:

 public class StackOverFlow {

static class Testy {

    int id;
    String name;

    public Testy(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 89 * hash + this.id;
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Testy other = (Testy) obj;
        if (this.id != other.id || !this.name.equals(other.name)) {
            return false;
        }
        return true;
    }

}

static class AnotherTesty {

    int id;
    String name;

    public AnotherTesty(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 41 * hash + this.id;
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final AnotherTesty other = (AnotherTesty) obj;
        if (this.id != other.id || !this.name.equals(other.name)) {
            return false;
        }
        return true;
    }

}

/**
 * @param args the command line arguments
 */
public static void main(String[] args) {

    List<Object> list = Arrays.asList(new Testy(5, "test"), new AnotherTesty(5, "test"));
    Set<Object> set = new HashSet<>(Arrays.asList(new Testy(5, "test"), new AnotherTesty(5, "test")));

    System.out.println(compareCollections(list, set, Testy.class, AnotherTesty.class));
}

private static boolean compareCollections(Collection<?> c1, Collection<?> c2, Class cls, Class cls2) {

    List<Object> listOfCls = c1.stream().filter(p -> cls.isInstance(p)).map(o -> cls.cast(o)).collect(Collectors.toList());
    List<Object> listOfCls2 = c1.stream().filter(p -> cls2.isInstance(p)).map(o -> cls2.cast(o)).collect(Collectors.toList());

    List<Object> list2OfCls = c2.stream().filter(p -> cls.isInstance(p)).map(o -> cls.cast(o)).collect(Collectors.toList());
    List<Object> list2OfCls2 = c2.stream().filter(p -> cls2.isInstance(p)).map(o -> cls2.cast(o)).collect(Collectors.toList());

    if (listOfCls.size() != list2OfCls.size()||listOfCls2.size() != list2OfCls2.size()) {
        return false;
    }

    boolean clsFlag = true, cls2Flag = true;
    for (int i = 0; i < listOfCls.size(); i++) {

        if (!listOfCls.get(i).equals(list2OfCls.get(i))) {
            clsFlag = false;
            break;
        }
    }
    for (int i = 0; i < list2OfCls2.size(); i++) {
        if (!listOfCls2.get(i).equals(list2OfCls2.get(i))) {
            cls2Flag = false;
            break;
        }
    }

    return clsFlag && cls2Flag;
}

}

答案 7 :(得分:1)

快速原型:

package stackoverflow;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;

import org.junit.Test;

public class CompareTwoList {
    static class ClassA {
        int intA;
        String strA;
        boolean boolA;

        // Constructor
        public ClassA(int intA, String strA, boolean boolA) {
            this.intA = intA;
            this.strA = strA;
            this.boolA = boolA;
        } // Getters and setters etc. below...


    }

    static class ClassB {
        int intB;
        String strB;
        boolean boolB;

        // Constructor
        public ClassB(int intB, String strB, boolean boolB) {
            this.intB = intB;
            this.strB = strB;
            this.boolB = boolB;
        } // Getters and setters etc. below...

    }

    @FunctionalInterface
    private interface IncopatibeEqualsOperator<A, B> extends BiFunction<A, B, Boolean> {
    }

    @Test
    public void CompareListOfClassAAndclassBObjects() throws Exception {
        List<ClassA> myList = Arrays.asList(
                new ClassA(1, "A", true),
                new ClassA(2, "B", true));

        Set<ClassB> mySet = new HashSet<ClassB>(Arrays.asList(
                new ClassB(1, "A", false),
                new ClassB(2, "B", false)));

        // can be extract to separate file
        IncopatibeEqualsOperator<ClassA, ClassB> equalsOperatorFlavor1 = (ClassA o1, ClassB o2) -> {
            // custom logic here
            return o1.intA == o2.intB &&
                    java.util.Objects.equals(o1.strA, o2.strB);
        };

        boolean areEquals = areEquals(myList, mySet, equalsOperatorFlavor1);

        assertThat(areEquals, is(true));
    }

    // Add in utility class
    private <A, B> boolean areEquals(Collection<A> o1, Collection<B> o2, IncopatibeEqualsOperator<A, B> comparator) {
        if (o1.size() == o2.size()) { // if size different; they are not equals
            for (A obj1 : o1) {
                boolean found = false; // search item of o1 into o2; algorithm
                                       // can be improve
                for (B obj2 : o2) {
                    if (comparator.apply(obj1, obj2)) { // call custom code of
                                                        // comparision
                        found = true;
                        break;
                    }
                }

                if (!found) {// if current element of o1 is not equals with any
                             // one return false
                    return false;
                }
            }
            return true;// all are matched
        }
        return false;
    }
}

答案 8 :(得分:1)

确保A类和B类具有toString()方法。

<强> ClassA的

public class ClassA {
    private int intA;
    private String strA;
    private boolean boolA;
    // Constructor
    public ClassA (int intA, String strA, boolean boolA) {
        this.intA = intA; this.strA = strA; this.boolA = boolA;
    } //

    @Override
    public String toString()
    {
        return intA + " " + strA + " " + boolA;
    }
}

<强> ClassB的

public class ClassB {
    private int intB;
    private String strB;
    private boolean boolB;
    // Constructor
    public ClassB (int intB, String strB, boolean boolB) {
        this.intB = intB; this.strB = strB; this.boolB = boolB;
    } // Gett

    @Override
    public String toString()
    {
        return intB + " " + strB + " " + boolB;
    }
}

主要/测试

public class JavaApplication11 {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        List<Object> myList = Arrays.asList(new ClassA(1, "A", true),
                                            new ClassA(2, "B", true));
        Set<Object> mySet = new HashSet<Object>(
                      Arrays.asList(new ClassB(1, "A", false),
                                    new ClassB(2, "B", false)));
        System.out.println("is equal: " + isEqual(myList, mySet));
    }

    static boolean isEqual(Object list, Object set)
    {
        System.out.println(list.toString());
        System.out.println(set.toString());
        String tempStringA = list.toString();
        tempStringA = tempStringA.replaceAll("true", "");
        tempStringA = tempStringA.replaceAll("false", "");

        String tempStringB = set.toString();
        tempStringB = tempStringB.replaceAll("true", "");
        tempStringB = tempStringB.replaceAll("false", "");


        return tempStringA.equals(tempStringB);
    }

}

答案 9 :(得分:1)

你应该采用EqualsBuilder的基本思想,但根据你的需要进行修改:创建某种类型的列表,与成员(或更好的吸气剂)进行比较,例如。一个HashMap。现在迭代此映射,使用映射的键条目在类A中搜索函数。接下来使用地图的值条目搜索B类的功能。调用(调用)两者并比较输出。

HashMap<String,String> mymap=new HashMap<>();
mymap.put("intA","intB");
mymap.put("boolA","boolB");

for(Map.Entry<String,String> e:mymap.entrySet()) { 
  // names not ok, maybe take a bean helper class
  Method m1=a.getClass().getMethod("get"+e.getKey()); // or look for fields if you dont have getters
  Method m2=b.getClass().getMethod("get"+e.getValue());
  Object r1=m1.invoke(a);
  Object r2=m2.invoke(b);
  if (!r1.equals(r2)) 
     return false;
}

很抱歉没有真正的代码。必须添加空检查!

答案 10 :(得分:0)

        public class Compare {

    public static void main(String[] args) {

        // TODO Auto-generated method stub
        Compare compare= new Compare();
        List<ClassA> myList = Arrays.asList(new ClassA(1, "A", false), new ClassA(2, "B", false));
        Set<ClassB> mySet = new HashSet<ClassB>(Arrays.asList(new ClassB(1, "A", false), new ClassB(2, "B", false)));

       System.out.println( compare.areEqual(myList,mySet));

    }
    public  boolean areEqual(Collection<ClassA> colA,Collection<ClassB> colB){
        boolean equal =false;
        if(colA.size()!=colB.size()){
            return equal;
        }
        Set<Integer> setA=new HashSet<Integer>();
        Set<Integer> setB= new HashSet<Integer>();
        for(ClassA obj : colA){
            setA.add(obj.hashCode());
        }
        for(ClassB obj : colB){
            setB.add(obj.hashCode());
        }
        if(setA.equals(setB)){
            equal=true;
        }
        return equal;
    }
}

class ClassA {
    private int intA;
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + intA;
        result = prime * result + ((strA == null) ? 0 : strA.hashCode());
        return result;
    }



    private String strA;
    private boolean boolA;

    // Constructor
    public ClassA(int intA, String strA, boolean boolA) {
        this.intA = intA;
        this.strA = strA;
        this.boolA = boolA;
    } // Getters and setters etc. below...


}

class ClassB {
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + intB;
        result = prime * result + ((strB == null) ? 0 : strB.hashCode());
        return result;
    }



    private int intB;
    private String strB;
    private boolean boolB;

    // Constructor
       public ClassB(int intB, String strB, boolean boolB) {
        this.intB = intB;
        this.strB = strB;
        this.boolB = boolB;
    } // Getters and setters etc. below...
}

我覆盖这两个类的哈希码方法,以基于int和str创建哈希码,并创建一个方法来创建Intergers集合,如果你不想要哈希码,整数就是每个类的哈希码覆盖让我知道也会更新

答案 11 :(得分:0)

愿它有所帮助......

class ClassA {

        private int intA;
        private String strA;
        private boolean boolA;

        // Constructor
        public ClassA(int intA, String strA, boolean boolA) {
            this.intA = intA;
            this.strA = strA;
            this.boolA = boolA;
        } // Getters and setters etc. below...

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof ClassA) {
                ClassA obj2 = (ClassA) obj;
                return (this.intA == obj2.intA && this.strA.equals(obj2.strA) && this.boolA == obj2.boolA);
            } else {
                ClassB obj2 = (ClassB) obj;
                return (this.intA == obj2.intB && this.strA.equals(obj2.strB) && this.boolA == obj2.boolB);
            }

        }

        @Override
        public int hashCode() {
            int hash = 3;
            hash = 71 * hash + this.intA;
            hash = 71 * hash + Objects.hashCode(this.strA);
            hash = 71 * hash + (this.boolA ? 1 : 0);
            return hash;
        }
    }

    class ClassB {

        private int intB;
        private String strB;
        private boolean boolB;

        // Constructor
        public ClassB(int intB, String strB, boolean boolB) {
            this.intB = intB;
            this.strB = strB;
            this.boolB = boolB;
        } // Getters and setters etc. below...

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof ClassB) {
                ClassB obj2 = (ClassB) obj;
                return (this.intB == obj2.intB && this.strB.equals(obj2.strB) && this.boolB == obj2.boolB);

            } else {
                ClassA obj2 = (ClassA) obj;
                return (this.intB == obj2.intA && this.strB.equals(obj2.strA) && this.boolB == obj2.boolA);
            }

        }

        @Override
        public int hashCode() {
            int hash = 5;
            hash = 79 * hash + this.intB;
            hash = 79 * hash + Objects.hashCode(this.strB);
            hash = 79 * hash + (this.boolB ? 1 : 0);
            return hash;
        }
    }

    public void test() {
        List<Object> myList = Arrays.asList(new ClassA(1, "A", true),
                new ClassA(1, "A", true));

        System.out.println(myList.get(0).equals(myList.get(1)));

    }

答案 12 :(得分:0)

对于两个单个元素,明确定义了等效比较,对于集合,可以进行等效比较的几个变体。一个方面是是否考虑元素排序。此外,当排序不重要时,等效元素的基数(匹配数)可能会或可能不会很重要。

因此,建议使用与EquivalenceComparisonBuilder一起使用的EquivalenceComparatorComparisonType ComparisonType.ORDERING ComparisonType.DUPLICATES进行严格排序,{{1对于严格匹配计数和ComparisonType.SIMPLE进行松散等价比较,其中一个集合中的每个元素至少是另一个集合中的一个等效元素就足够了。

请注意,如果集合可能包含EquivalenceComparator个元素,null的实现需要考虑null个参数。

package equivalence;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.Predicate;

public class Equivalence {

    public static interface EquivalenceComparison<S, T> {
        boolean equivalent();
    }

    public static interface EquivalenceComparator<S, T> {
        boolean equivalent(S s, T t);
    }


    static public class EquivalenceComparisonBuilder<S, T> {

        enum ComparisonType {
            ORDERING, DUPLICATES, SIMPLE
        };

        private Collection<S> ss;
        private Collection<T> ts;
        private EquivalenceComparator<S, T> ec;
        private ComparisonType comparisonType;

        public EquivalenceComparisonBuilder<S, T> setCollections(Collection<S> ss, Collection<T> ts) {
            this.ss = ss;
            this.ts = ts;
            return this;
        }

        public EquivalenceComparisonBuilder<S, T> setEquivalenceComparator(EquivalenceComparator<S, T> ec) {
            this.ec = ec;
            return this;
        }

        public EquivalenceComparisonBuilder<S, T> setComparisonType(ComparisonType comparisonType) {
            this.comparisonType = comparisonType;
            return this;
        }

        public EquivalenceComparison<S, T> comparison() {
            if (comparisonType == null || ss == null || ts == null) {
                throw new NullPointerException();
            }
            switch (comparisonType) {
            case ORDERING:
                return new OrderingComparison<S, T>(ss, ts, ec);
            case DUPLICATES:
                return new DuplicatesComparison<S, T>(ss, ts, ec);
            case SIMPLE:
                return new SimpleComparison<S, T>(ss, ts, ec);
            default:
                throw new IllegalArgumentException("Unknown comparison type");
            }
        }

    }


    private static <S, T> EquivalenceComparator<T, S> mirrored(EquivalenceComparator<S, T> ec) {
        return new EquivalenceComparator<T, S>() {
            @Override
            public boolean equivalent(T t, S s) {
                return ec.equivalent(s, t);
            }
        };
    }


    private static class EquivalencePredicate<S, T> implements Predicate<T> {

        private S s;
        private EquivalenceComparator<S, T> equivalenceComparator;

        public EquivalencePredicate(S s, EquivalenceComparator<S, T> equivalenceComparator) {
            this.s = s;
            this.equivalenceComparator = equivalenceComparator;
        }

        @Override
        public boolean evaluate(T t) {
            return equivalenceComparator.equivalent(s, t);
        }
    }

    static private class OrderingComparison<S, T> implements EquivalenceComparison<S, T> {

        private Collection<S> ss;
        private Collection<T> ts;
        private EquivalenceComparator<S, T> ec;

        public OrderingComparison(Collection<S> ss, Collection<T> ts, EquivalenceComparator<S, T> ec) {
            this.ss = ss;
            this.ts = ts;
            this.ec = ec;
        }

        @Override
        public boolean equivalent() {
            if (ss.size() != ts.size()) {
                return false;
            }
            List<S> ssl = new ArrayList<S>(ss);
            List<T> tsl = new ArrayList<T>(ts);

            for (int i = 0; i < ssl.size(); i++) {
                S s = ssl.get(i);
                T t = tsl.get(i);
                if (!ec.equivalent(s, t)) {
                    return false;
                }
            }
            return true;
        }
    }

    static private class DuplicatesComparison<S, T> implements EquivalenceComparison<S, T> {

        private Collection<S> ss;
        private Collection<T> ts;
        private EquivalenceComparator<S, T> ec;

        public DuplicatesComparison(Collection<S> ss, Collection<T> ts, EquivalenceComparator<S, T> ec) {
            this.ss = ss;
            this.ts = ts;
            this.ec = ec;
        }

        @Override
        public boolean equivalent() {
            if (ss.size() != ts.size()) {
                return false;
            }

            for (S s : ss) {
                Collection<T> matchingTs = CollectionUtils.select(ts, new EquivalencePredicate(s, ec));
                if (matchingTs.size() == 0) {
                    return false;
                }

                T t = matchingTs.iterator().next();
                Collection<S> matchingSs = CollectionUtils.select(ss, new EquivalencePredicate(t, mirrored(ec)));

                if (matchingTs.size() != matchingSs.size()) {
                    return false;
                }
            }
            return true;
        }
    }

    static private class SimpleComparison<S, T> implements EquivalenceComparison<S, T> {

        private Collection<S> ss;
        private Collection<T> ts;
        private EquivalenceComparator<S, T> ec;

        public SimpleComparison(Collection<S> ss, Collection<T> ts, EquivalenceComparator<S, T> ec) {
            this.ss = ss;
            this.ts = ts;
            this.ec = ec;
        }

        @Override
        public boolean equivalent() {           
            for (S s : ss) {
                if (!CollectionUtils.exists(ts, new EquivalencePredicate(s, ec))) {
                    return false;
                }
            }
            for(T t :ts) {
                if (!CollectionUtils.exists(ss, new EquivalencePredicate(t, mirrored(ec)))) {
                    return false;
                }
            }
            return true;
        }

    }
}

以下是一些测试用例:

package equivalence;

import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.junit.Assert;
import org.junit.Test;

import equivalence.Equivalence.EquivalenceComparator;
import equivalence.Equivalence.EquivalenceComparisonBuilder;
import equivalence.Equivalence.EquivalenceComparisonBuilder.ComparisonType;

public class EquivalenceExample {

    static class A {

        private int ia;

        private String is;

        private long a;

        public A(int ia, String is, long a) {
            this.ia = ia;
            this.is = is;
            this.a = a;
        }

        public int getIa() {
            return ia;
        }

        public String getIs() {
            return is;
        }

        public long getA() {
            return a;
        }

    }

    static class B {

        private int ib;

        private String is;

        private long b;

        public B(int ib, String is, long b) {
            this.ib = ib;
            this.is = is;
            this.b = b;
        }

        public int getIb() {
            return ib;
        }

        public String getIs() {
            return is;
        }

        public long getB() {
            return b;
        }

    }

    static class ABEquivalenceComparator implements EquivalenceComparator<A, B> {

        static public ABEquivalenceComparator INSTANCE = new ABEquivalenceComparator();

        @Override
        public boolean equivalent(A a, B b) {
            return new EqualsBuilder().append(a.getIa(), b.getIb()).append(a.getIs(), b.getIs()).isEquals();
        }
    }

    @Test
    public void thatOrderingEquivalenceMatchesEquivalentElementsWhenInSameOrder() {
        List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l));
        LinkedHashSet<B> bs = new LinkedHashSet<B>(Arrays.asList(new B(1, "1", 99l), new B(2, "2", 99l)));

        Assert.assertTrue(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
                .setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.ORDERING)
                .comparison().equivalent());
    }

    @Test
    public void thatOrderingEquivalenceDoesNotMatchEquivalentElementsWhenNotSameOrdering() {
        List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l));
        LinkedHashSet<B> bs = new LinkedHashSet<B>(Arrays.asList(new B(2, "2", 99l), new B(1, "1", 99l)));

        Assert.assertFalse(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
                .setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.ORDERING)
                .comparison().equivalent());
    }

    @Test
    public void thatOrderingEquivalenceDoesNotMatchNonEquivalentElements() {
        List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l));
        LinkedHashSet<B> bs = new LinkedHashSet<B>(Arrays.asList(new B(1, "1", 99l), new B(1, "1", 99l)));

        Assert.assertFalse(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
                .setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.ORDERING)
                .comparison().equivalent());
    }

    @Test
    public void thatDuplicatesEquivalenceMatchesEquivalentElementsRegardlessOrder() {
        List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l));
        LinkedHashSet<B> bs = new LinkedHashSet<B>(Arrays.asList(new B(2, "2", 99l), new B(1, "1", 99l)));

        Assert.assertTrue(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
                .setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.DUPLICATES)
                .comparison().equivalent());
    }

    @Test
    public void thatDuplicatesEquivalenceDoesNotMatchesWhenElementsCardinlityDoNotMatch() {
        List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l), new A(1, "1", 99l));
        LinkedHashSet<B> bs = new LinkedHashSet<B>(
                Arrays.asList(new B(2, "2", 99l), new B(1, "1", 99l), new B(2, "2", 99l)));

        Assert.assertFalse(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
                .setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.DUPLICATES)
                .comparison().equivalent());
    }

    @Test
    public void thatSimpleEquivalenceMatchesRegardlessEquivalentElementCardinality() {
        List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l), new A(1, "1", 99l));
        LinkedHashSet<B> bs = new LinkedHashSet<B>(
                Arrays.asList(new B(2, "2", 99l), new B(1, "1", 99l), new B(2, "2", 99l)));

        Assert.assertTrue(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
                .setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.SIMPLE)
                .comparison().equivalent());
    }

    @Test
    public void thatSimpleEquivalenceMatchesRegardlessElementsCount() {
        List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l));
        LinkedHashSet<B> bs = new LinkedHashSet<B>(
                Arrays.asList(new B(2, "2", 99l), new B(1, "1", 99l), new B(2, "2", 99l)));

        Assert.assertTrue(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
                .setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.SIMPLE)
                .comparison().equivalent());
    }

    @Test
    public void thatSimpleEquivalenceDoesMatchesWhenElementsDoNotMatch() {
        List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l));
        LinkedHashSet<B> bs = new LinkedHashSet<B>(Arrays.asList(new B(2, "2", 99l), new B(3, "3", 99l)));

        Assert.assertFalse(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
                .setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.SIMPLE)
                .comparison().equivalent());
    }

    }