如何根据来自不同 Java 类 的字段比较 "equivalence" 的两个集合?
How to compare two Collections for "equivalence" based on fields from different Java classes?
给定任意两个 类,例如ClassA
和 ClassB
以下:
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
是否 "equivalent"(*) 的最简单方法是什么?
(*) 使用词 "equivalent" 而不是 "equal" 因为这是上下文相关的 - 即这样的 "equivalence" 可能在另一个上下文中有不同的定义。
上面的工作示例:
假设我们指定 intA
和 strA
应分别与 intB
和 strB
匹配(但可以忽略 boolA
/ boolB
值)。这将使上面定义的两个集合对象被认为是等价的——但如果一个元素被添加到其中一个集合或从其中一个集合中删除,那么它们就不再是等价的了。
首选方案:使用的方法对任何Collection
类型都应该是通用的。理想情况下 Java 7 仅限于使用它(但 Java 8 可能对其他人有额外的兴趣)。乐于使用 Guava 或 Apache Commons,但不愿使用更晦涩的外部库。
没有简单的方法。
与常规 Java 集合一起使用的最通用的方法是创建一个包装器 class,它将 ClassA
或 ClassB
作为输入,然后覆盖您定义的 equals/hashcode。
在某些情况下,您可以滥用 Comparator
,但这会限制您使用 TreeMap/TreeSet
。
您还可以实现 equals()
方法来使 classA.equals(classB);
returns 为真,但如果您不小心,这可能会导致棘手的错误。它还可能导致 a.equals(b)
和 b.equals(c)
但 !a.equals(c)
.
的有趣情况
一些库(Guava?)也有一个 Comparator
风格的相等性测试机制,但它只适用于库的集合。
Apache Commons Lang 有 EqualsBuilder#reflectionEquals(Object, Object)
:
This method uses reflection to determine if the two Object
s are equal.
It uses AccessibleObject.setAccessible
to gain access to private
fields. This means that it will throw a security exception if run
under a security manager, if the permissions are not set up correctly.
It is also not as efficient as testing explicitly. Non-primitive
fields are compared using equals()
.
Transient members will be not be tested, as they are likely derived
fields, and not part of the value of the Object
.
Static fields will not be tested. Superclass fields will be included.
所以这应该涵盖您的用例。明显的免责声明:它使用反射 ;)
编辑: 当然,这假设字段具有相同的名称,而不是类型。在后一种情况下,可以检查 source code 并根据他们的用例进行调整。
另一种可能的解决方案是编写一个带有谓词的简单比较方法(因此您可以明确指定两个 类 的条件在您的条件下是相似的)。我在 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 代码 allMatch 和 anyMatch(每个 for 循环就足够了)将它移植到 Java 7
编辑: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。
单元测试:
class UnitTestAppleClass {
private int appleKey;
private String appleName;
public UnitTestAppleClass(int appleKey, String appleName) {
this.appleKey = appleKey;
this.appleName = appleName;
}
public int getAppleKey() {
return appleKey;
}
public String getAppleName() {
return appleName;
}
public void setAppleName(String appleName) {
this.appleName = appleName;
}
}
class UnitTestOrangeClass {
private int orangeKey;
private String orangeName;
public UnitTestOrangeClass(int orangeKey, String orangeName) {
this.orangeKey = orangeKey;
this.orangeName = orangeName;
}
public int getOrangeKey() {
return orangeKey;
}
public String getOrangeName() {
return orangeName;
}
}
和
@Test
public void compareNotSameTypeCollectionsOkTest() {
BiPredicate<UnitTestAppleClass, UnitTestOrangeClass> myApplesToOrangesCompareBiPred = (app, org) -> {
/* you CAN compare apples and oranges */
boolean returnValue =
app.getAppleKey() == org.getOrangeKey() && app.getAppleName().equals(org.getOrangeName());
return returnValue;
};
UnitTestAppleClass apple1 = new UnitTestAppleClass(1111, "Fruit1111");
UnitTestAppleClass apple2 = new UnitTestAppleClass(1112, "Fruit1112");
UnitTestAppleClass apple3 = new UnitTestAppleClass(1113, "Fruit1113");
UnitTestAppleClass apple4 = new UnitTestAppleClass(1114, "Fruit1114");
Collection<UnitTestAppleClass> apples = asList(apple1, apple2, apple3, apple4);
/* same "key" VALUES, and "name" VALUES, but different property names */
UnitTestOrangeClass orange1 = new UnitTestOrangeClass(1111, "Fruit1111");
UnitTestOrangeClass orange2 = new UnitTestOrangeClass(1112, "Fruit1112");
UnitTestOrangeClass orange3 = new UnitTestOrangeClass(1113, "Fruit1113");
UnitTestOrangeClass orange4 = new UnitTestOrangeClass(1114, "Fruit1114");
Collection<UnitTestOrangeClass> oranges = asList(orange1, orange2, orange3, orange4);
boolean applesAndOrangeCheck = EqualsHelper.<UnitTestAppleClass, UnitTestOrangeClass> compareCollections(apples, oranges, myApplesToOrangesCompareBiPred);
assertTrue(applesAndOrangeCheck);
/* alter one of the apples */
if( apples.stream().findFirst().isPresent())
{
apples.stream().findFirst().get().setAppleName("AppleChangedNameOne");
boolean alteredAppleAndOrangeCheck = EqualsHelper.<UnitTestAppleClass, UnitTestOrangeClass> compareCollections(apples, oranges, myApplesToOrangesCompareBiPred);
assertFalse(alteredAppleAndOrangeCheck);
}
Collection<UnitTestAppleClass> reducedApples = asList(apple1, apple2, apple3);
boolean reducedApplesAndOrangeCheck = EqualsHelper.<UnitTestAppleClass, UnitTestOrangeClass> compareCollections(reducedApples, oranges, myApplesToOrangesCompareBiPred);
assertFalse(reducedApplesAndOrangeCheck);
}
这是我的回答:
public class Whosebug {
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;
}
}
两个现有答案的组合:包装器的通用版本 class Kayaman 建议(只是一个列表)。使用 ArrayList::equals 作为 Przemek Gumula 方法的谓词。
我添加了一个生成器以使其更好用:
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));
}
}
}
快速原型:
package Whosebug;
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;
}
}
What's the simplest way of telling whether the two Collections
are equal in terms of a specified subset of fields?
根据您的描述,您对平等的要求是:
- 集合大小相等。
- 对于
collection1
中的每个item1
,在collection2
中存在item2
使得item1.field_x
等于item2.field_y
,对于多个定义 field_x
-field_y
对。
如果我们可以假设两个集合中都没有重复元素,
也就是说,那么 "simplest way" 可能是这样的:
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)
在第二个列表中被认为是等价的。
上面的算法会发现这两个集合相等,这是不正确的。
可以处理重复的情况,同时以使用额外 space:
为代价提高时间复杂度
- 遍历第一个集合以构建
(int, String)
个元组的计数图
- 在检查计数图的同时迭代第二个集合:
- 如果映射计数不包含相应的
(int, String)
元组,return false
,因为这意味着元素没有匹配对
- 如果存在相应的元组,则减少其计数
- 如果计数达到0,从map中的元组
- 如果到达循环的末尾,那一定意味着所有项目都匹配(地图应该是空的),所以你可以简单地
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);
}
}
}
一些 assertj 测试来验证实现:
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();
作为进一步的改进,
可以使 FieldExtractingEqual
的实现通用,
这样它就可以接受任意 Collection<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));
}
}
}
示例用法和一些 assertj 测试:
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
实例,例如:
.addExtractors(ClassA::getIntA, ClassB::getIntB)
第一个参数是一个对象,提取第一个字段class,
第二个参数是一个对象,提取第二个class中对应的字段。
您可以添加任意数量的提取器对来比较相等条件。
虽然我使用Java8写作风格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();
}
}
)
newTuple*
实用方法也是如此。
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...
}
好吧,我重写了 class 的哈希码方法,以在 int 和 str 的基础上创建哈希码,以及一种创建整数集的方法,整数是每个 class 的哈希码,如果你甚至不希望哈希码被覆盖让我知道也会为此更新
确保 Class 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;
}
}
Main/Test
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);
}
}
您应该采用 EqualsBuilder 的基本思想,但根据您的需要进行修改:创建某种包含成员(或更好的吸气剂)的列表以进行比较,例如。一个哈希图。现在遍历这个map,用map的key入口搜索classA中的函数。接下来用map的值入口搜索classB的函数。调用(调用)两者并比较输出。
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;
}
抱歉没有真正的代码。必须添加空检查!
可能有帮助..
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)));
}
这是一个使用 lambda 和 higher-order 函数的 Java 8 版本。可以使用匿名内部 类 而不是 lambda 将其转换为 Java 7。 (我相信大多数 IDE 都有执行此操作的重构操作。)我将把它留作感兴趣的读者的练习。
这里实际上有两个不同的问题:
给定两个不同类型的 object,通过检查每个类型的相应字段来评估它们。这与 JDK 库 API 已经定义的 "equals" 和 "compare" 操作不同,因此我将使用术语 "equivalent" 代替。
给定两个 collection 包含这些类型的元素,确定它们是否是 "equals" 该术语的某些定义。这实际上很微妙;请参阅下面的讨论。
1.等效
给定两个 object 类型 T
和 U
我们想确定它们是否等价。结果是一个布尔值。这可以用 BiPredicate<T,U>
类型的函数表示。但是我们不一定能直接检查 objects;相反,我们需要从每个 object 中提取各自的字段,并相互评估提取结果。如果从 T
中提取的字段是 TR
类型,而从 U
中提取的字段是 UR
类型,则提取器由函数类型
表示]
Function<T, TR>
Function<U, UR>
现在我们已经提取了 TR
和 UR
类型的结果。我们可以只对它们调用 equals()
,但这是不必要的限制。相反,我们可以提供另一个等价函数,该函数将被调用来相互评估这两个结果。那是 BiPredicate<TR,UR>
.
考虑到所有这些,我们可以编写一个 higher-order 函数,它接受所有这些函数并为我们生成等价函数(包含通配符以确保完整性):
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
参数,我们实际上并不关心函数 return 类型是什么,因此通配符。
以下是仅使用字符串字段使用它来评估 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 示例中 类 的 int
和 String
字段创建一个评估等价性的函数。为此,最好将函数存储在一个变量中,而不是使用它,尽管这可能都是内联的(我认为这会使它不可读):
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。 Collection“平等”
第二部分是判断两个collection在某种意义上是否是"equals"。问题是在 Collections Framework 中,相等的概念被定义为一个 List 只能等于另一个 List,一个 Set 只能等于另一个 Set。因此,其他类型的 Collection 永远不能等于 List 或 Set。有关这一点的一些讨论,请参阅 Collection.equals()
的规范。
这显然与OP想要的不一致。正如 OP 所建议的,我们并不是真的想要 "equality," 但我们想要一些其他的 属性 我们需要为其提供定义。基于 OP 的示例,以及 Przemek Gumula and janos, it seems like we want the elements in the two collections to somehow be in one-for-one correspondence. I'll call this a bijection 在其他答案中的一些建议,这些建议在数学上可能不精确,但似乎足够接近。此外,每对元素之间的对应关系应为等价,如上定义。
计算这个有点微妙,因为我们有自己的等价关系。我们不能使用许多 built-in Collection 的操作,因为它们都使用 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给的基本一样。)这个有问题,归结为一个collection中的一个元素对应的可能不止一个元素另一个 collection,留下不匹配的元素。如果给定两个多重集,使用等式作为等价物,这会给出奇怪的结果ce函数:
{a x 2, b} // essentially {a, a, b}
{a, b x 2} // essentially {a, b, b}
此函数将这两个多重集视为双射,但显然不是这样。如果等价函数允许 many-to-one 匹配,则会出现另一个问题:
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
。这显然是错误的。
另一种算法是创建一个临时结构并在元素匹配时删除它们。该结构必须考虑重复项,因此我们需要减少计数,并且仅在计数达到零时才删除元素。幸运的是,各种 Java8 功能使这变得非常简单。这与 janos 的答案中使用的算法非常相似,尽管我已将等价函数提取到方法参数中。 las,因为我的等价函数可以有嵌套的等价函数,这意味着我不能探测映射(由相等定义)。相反,我必须搜索地图的键,这意味着算法是 O(N^2)。好吧。
但是,代码非常简单。首先,使用 groupingBy
从第二个 collection 生成频率图。然后,迭代第一个 collection 的元素,并搜索频率图的键以寻找等效项。如果找到一个,则减少其出现次数。注意传递给 Map.compute()
的重映射函数中 null
的 return 值。这具有 删除 条目的副作用,而不是将映射设置为 null
。这有点 API 技巧,但非常有效。
对于第一个 collection 中的每个元素,必须找到第二个 collection 中的等效元素,否则它会退出。第一个collection的所有元素都处理完之后,frequency map的所有元素也应该都处理完了,所以简单测试一下是否为空。
代码如下:
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
将失败。还有一些自由度没有考虑在内。例如,假设 collection 是
{a, b}
{x, y}
而a
相当于x
和y
,但b
只相当于x
。如果a
匹配到x
,isBijection
的结果是false
。但是如果 a
与 y
匹配,结果将是 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
。
虽然对于两个单个元素,等价比较是明确定义的,但对于集合,等价比较的几种变体是可能的。一方面是是否考虑元素排序。此外,当排序不重要时,等效元素的基数(匹配数)可能重要也可能不重要。
因此建议使用 EquivalenceComparisonBuilder
与两个集合和 EquivalenceComparator
一起配置 ComparisonType
- ComparisonType.ORDERING
用于严格排序,ComparisonType.DUPLICATES
用于严格匹配计数,ComparisonType.SIMPLE
用于松散等价比较,其中一个集合中的每个元素在另一个集合中至少有一个等价元素就足够了。
请注意,如果集合可能包含 null
个元素,EquivalenceComparator
的实现需要考虑 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());
}
}
给定任意两个 类,例如ClassA
和 ClassB
以下:
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
是否 "equivalent"(*) 的最简单方法是什么?
(*) 使用词 "equivalent" 而不是 "equal" 因为这是上下文相关的 - 即这样的 "equivalence" 可能在另一个上下文中有不同的定义。
上面的工作示例:
假设我们指定 intA
和 strA
应分别与 intB
和 strB
匹配(但可以忽略 boolA
/ boolB
值)。这将使上面定义的两个集合对象被认为是等价的——但如果一个元素被添加到其中一个集合或从其中一个集合中删除,那么它们就不再是等价的了。
首选方案:使用的方法对任何Collection
类型都应该是通用的。理想情况下 Java 7 仅限于使用它(但 Java 8 可能对其他人有额外的兴趣)。乐于使用 Guava 或 Apache Commons,但不愿使用更晦涩的外部库。
没有简单的方法。
与常规 Java 集合一起使用的最通用的方法是创建一个包装器 class,它将 ClassA
或 ClassB
作为输入,然后覆盖您定义的 equals/hashcode。
在某些情况下,您可以滥用 Comparator
,但这会限制您使用 TreeMap/TreeSet
。
您还可以实现 equals()
方法来使 classA.equals(classB);
returns 为真,但如果您不小心,这可能会导致棘手的错误。它还可能导致 a.equals(b)
和 b.equals(c)
但 !a.equals(c)
.
一些库(Guava?)也有一个 Comparator
风格的相等性测试机制,但它只适用于库的集合。
Apache Commons Lang 有 EqualsBuilder#reflectionEquals(Object, Object)
:
This method uses reflection to determine if the two
Object
s are equal.It uses
AccessibleObject.setAccessible
to gain access to private fields. This means that it will throw a security exception if run under a security manager, if the permissions are not set up correctly. It is also not as efficient as testing explicitly. Non-primitive fields are compared usingequals()
.Transient members will be not be tested, as they are likely derived fields, and not part of the value of the
Object
.Static fields will not be tested. Superclass fields will be included.
所以这应该涵盖您的用例。明显的免责声明:它使用反射 ;)
编辑: 当然,这假设字段具有相同的名称,而不是类型。在后一种情况下,可以检查 source code 并根据他们的用例进行调整。
另一种可能的解决方案是编写一个带有谓词的简单比较方法(因此您可以明确指定两个 类 的条件在您的条件下是相似的)。我在 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 代码 allMatch 和 anyMatch(每个 for 循环就足够了)将它移植到 Java 7
编辑: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。
单元测试:
class UnitTestAppleClass {
private int appleKey;
private String appleName;
public UnitTestAppleClass(int appleKey, String appleName) {
this.appleKey = appleKey;
this.appleName = appleName;
}
public int getAppleKey() {
return appleKey;
}
public String getAppleName() {
return appleName;
}
public void setAppleName(String appleName) {
this.appleName = appleName;
}
}
class UnitTestOrangeClass {
private int orangeKey;
private String orangeName;
public UnitTestOrangeClass(int orangeKey, String orangeName) {
this.orangeKey = orangeKey;
this.orangeName = orangeName;
}
public int getOrangeKey() {
return orangeKey;
}
public String getOrangeName() {
return orangeName;
}
}
和
@Test
public void compareNotSameTypeCollectionsOkTest() {
BiPredicate<UnitTestAppleClass, UnitTestOrangeClass> myApplesToOrangesCompareBiPred = (app, org) -> {
/* you CAN compare apples and oranges */
boolean returnValue =
app.getAppleKey() == org.getOrangeKey() && app.getAppleName().equals(org.getOrangeName());
return returnValue;
};
UnitTestAppleClass apple1 = new UnitTestAppleClass(1111, "Fruit1111");
UnitTestAppleClass apple2 = new UnitTestAppleClass(1112, "Fruit1112");
UnitTestAppleClass apple3 = new UnitTestAppleClass(1113, "Fruit1113");
UnitTestAppleClass apple4 = new UnitTestAppleClass(1114, "Fruit1114");
Collection<UnitTestAppleClass> apples = asList(apple1, apple2, apple3, apple4);
/* same "key" VALUES, and "name" VALUES, but different property names */
UnitTestOrangeClass orange1 = new UnitTestOrangeClass(1111, "Fruit1111");
UnitTestOrangeClass orange2 = new UnitTestOrangeClass(1112, "Fruit1112");
UnitTestOrangeClass orange3 = new UnitTestOrangeClass(1113, "Fruit1113");
UnitTestOrangeClass orange4 = new UnitTestOrangeClass(1114, "Fruit1114");
Collection<UnitTestOrangeClass> oranges = asList(orange1, orange2, orange3, orange4);
boolean applesAndOrangeCheck = EqualsHelper.<UnitTestAppleClass, UnitTestOrangeClass> compareCollections(apples, oranges, myApplesToOrangesCompareBiPred);
assertTrue(applesAndOrangeCheck);
/* alter one of the apples */
if( apples.stream().findFirst().isPresent())
{
apples.stream().findFirst().get().setAppleName("AppleChangedNameOne");
boolean alteredAppleAndOrangeCheck = EqualsHelper.<UnitTestAppleClass, UnitTestOrangeClass> compareCollections(apples, oranges, myApplesToOrangesCompareBiPred);
assertFalse(alteredAppleAndOrangeCheck);
}
Collection<UnitTestAppleClass> reducedApples = asList(apple1, apple2, apple3);
boolean reducedApplesAndOrangeCheck = EqualsHelper.<UnitTestAppleClass, UnitTestOrangeClass> compareCollections(reducedApples, oranges, myApplesToOrangesCompareBiPred);
assertFalse(reducedApplesAndOrangeCheck);
}
这是我的回答:
public class Whosebug {
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;
}
}
两个现有答案的组合:包装器的通用版本 class Kayaman 建议(只是一个列表)。使用 ArrayList::equals 作为 Przemek Gumula 方法的谓词。
我添加了一个生成器以使其更好用:
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));
}
}
}
快速原型:
package Whosebug;
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;
}
}
What's the simplest way of telling whether the two
Collections
are equal in terms of a specified subset of fields?
根据您的描述,您对平等的要求是:
- 集合大小相等。
- 对于
collection1
中的每个item1
,在collection2
中存在item2
使得item1.field_x
等于item2.field_y
,对于多个定义field_x
-field_y
对。
如果我们可以假设两个集合中都没有重复元素, 也就是说,那么 "simplest way" 可能是这样的:
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)
在第二个列表中被认为是等价的。
上面的算法会发现这两个集合相等,这是不正确的。
可以处理重复的情况,同时以使用额外 space:
为代价提高时间复杂度- 遍历第一个集合以构建
(int, String)
个元组的计数图 - 在检查计数图的同时迭代第二个集合:
- 如果映射计数不包含相应的
(int, String)
元组,return false
,因为这意味着元素没有匹配对 - 如果存在相应的元组,则减少其计数
- 如果计数达到0,从map中的元组
- 如果映射计数不包含相应的
- 如果到达循环的末尾,那一定意味着所有项目都匹配(地图应该是空的),所以你可以简单地
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);
}
}
}
一些 assertj 测试来验证实现:
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();
作为进一步的改进,
可以使 FieldExtractingEqual
的实现通用,
这样它就可以接受任意 Collection<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));
}
}
}
示例用法和一些 assertj 测试:
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
实例,例如:
.addExtractors(ClassA::getIntA, ClassB::getIntB)
第一个参数是一个对象,提取第一个字段class, 第二个参数是一个对象,提取第二个class中对应的字段。 您可以添加任意数量的提取器对来比较相等条件。
虽然我使用Java8写作风格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();
}
}
)
newTuple*
实用方法也是如此。
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...
}
好吧,我重写了 class 的哈希码方法,以在 int 和 str 的基础上创建哈希码,以及一种创建整数集的方法,整数是每个 class 的哈希码,如果你甚至不希望哈希码被覆盖让我知道也会为此更新
确保 Class 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;
}
}
Main/Test
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);
}
}
您应该采用 EqualsBuilder 的基本思想,但根据您的需要进行修改:创建某种包含成员(或更好的吸气剂)的列表以进行比较,例如。一个哈希图。现在遍历这个map,用map的key入口搜索classA中的函数。接下来用map的值入口搜索classB的函数。调用(调用)两者并比较输出。
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;
}
抱歉没有真正的代码。必须添加空检查!
可能有帮助..
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)));
}
这是一个使用 lambda 和 higher-order 函数的 Java 8 版本。可以使用匿名内部 类 而不是 lambda 将其转换为 Java 7。 (我相信大多数 IDE 都有执行此操作的重构操作。)我将把它留作感兴趣的读者的练习。
这里实际上有两个不同的问题:
给定两个不同类型的 object,通过检查每个类型的相应字段来评估它们。这与 JDK 库 API 已经定义的 "equals" 和 "compare" 操作不同,因此我将使用术语 "equivalent" 代替。
给定两个 collection 包含这些类型的元素,确定它们是否是 "equals" 该术语的某些定义。这实际上很微妙;请参阅下面的讨论。
1.等效
给定两个 object 类型 T
和 U
我们想确定它们是否等价。结果是一个布尔值。这可以用 BiPredicate<T,U>
类型的函数表示。但是我们不一定能直接检查 objects;相反,我们需要从每个 object 中提取各自的字段,并相互评估提取结果。如果从 T
中提取的字段是 TR
类型,而从 U
中提取的字段是 UR
类型,则提取器由函数类型
Function<T, TR>
Function<U, UR>
现在我们已经提取了 TR
和 UR
类型的结果。我们可以只对它们调用 equals()
,但这是不必要的限制。相反,我们可以提供另一个等价函数,该函数将被调用来相互评估这两个结果。那是 BiPredicate<TR,UR>
.
考虑到所有这些,我们可以编写一个 higher-order 函数,它接受所有这些函数并为我们生成等价函数(包含通配符以确保完整性):
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
参数,我们实际上并不关心函数 return 类型是什么,因此通配符。
以下是仅使用字符串字段使用它来评估 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 示例中 类 的 int
和 String
字段创建一个评估等价性的函数。为此,最好将函数存储在一个变量中,而不是使用它,尽管这可能都是内联的(我认为这会使它不可读):
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。 Collection“平等”
第二部分是判断两个collection在某种意义上是否是"equals"。问题是在 Collections Framework 中,相等的概念被定义为一个 List 只能等于另一个 List,一个 Set 只能等于另一个 Set。因此,其他类型的 Collection 永远不能等于 List 或 Set。有关这一点的一些讨论,请参阅 Collection.equals()
的规范。
这显然与OP想要的不一致。正如 OP 所建议的,我们并不是真的想要 "equality," 但我们想要一些其他的 属性 我们需要为其提供定义。基于 OP 的示例,以及 Przemek Gumula and janos, it seems like we want the elements in the two collections to somehow be in one-for-one correspondence. I'll call this a bijection 在其他答案中的一些建议,这些建议在数学上可能不精确,但似乎足够接近。此外,每对元素之间的对应关系应为等价,如上定义。
计算这个有点微妙,因为我们有自己的等价关系。我们不能使用许多 built-in Collection 的操作,因为它们都使用 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给的基本一样。)这个有问题,归结为一个collection中的一个元素对应的可能不止一个元素另一个 collection,留下不匹配的元素。如果给定两个多重集,使用等式作为等价物,这会给出奇怪的结果ce函数:
{a x 2, b} // essentially {a, a, b}
{a, b x 2} // essentially {a, b, b}
此函数将这两个多重集视为双射,但显然不是这样。如果等价函数允许 many-to-one 匹配,则会出现另一个问题:
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
。这显然是错误的。
另一种算法是创建一个临时结构并在元素匹配时删除它们。该结构必须考虑重复项,因此我们需要减少计数,并且仅在计数达到零时才删除元素。幸运的是,各种 Java8 功能使这变得非常简单。这与 janos 的答案中使用的算法非常相似,尽管我已将等价函数提取到方法参数中。 las,因为我的等价函数可以有嵌套的等价函数,这意味着我不能探测映射(由相等定义)。相反,我必须搜索地图的键,这意味着算法是 O(N^2)。好吧。
但是,代码非常简单。首先,使用 groupingBy
从第二个 collection 生成频率图。然后,迭代第一个 collection 的元素,并搜索频率图的键以寻找等效项。如果找到一个,则减少其出现次数。注意传递给 Map.compute()
的重映射函数中 null
的 return 值。这具有 删除 条目的副作用,而不是将映射设置为 null
。这有点 API 技巧,但非常有效。
对于第一个 collection 中的每个元素,必须找到第二个 collection 中的等效元素,否则它会退出。第一个collection的所有元素都处理完之后,frequency map的所有元素也应该都处理完了,所以简单测试一下是否为空。
代码如下:
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
将失败。还有一些自由度没有考虑在内。例如,假设 collection 是
{a, b}
{x, y}
而a
相当于x
和y
,但b
只相当于x
。如果a
匹配到x
,isBijection
的结果是false
。但是如果 a
与 y
匹配,结果将是 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
。
虽然对于两个单个元素,等价比较是明确定义的,但对于集合,等价比较的几种变体是可能的。一方面是是否考虑元素排序。此外,当排序不重要时,等效元素的基数(匹配数)可能重要也可能不重要。
因此建议使用 EquivalenceComparisonBuilder
与两个集合和 EquivalenceComparator
一起配置 ComparisonType
- ComparisonType.ORDERING
用于严格排序,ComparisonType.DUPLICATES
用于严格匹配计数,ComparisonType.SIMPLE
用于松散等价比较,其中一个集合中的每个元素在另一个集合中至少有一个等价元素就足够了。
请注意,如果集合可能包含 null
个元素,EquivalenceComparator
的实现需要考虑 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());
}
}