如何比较具有相似属性的两个不同对象列表



如果我有来自如下两个不同类的两个实例(具有相似的属性(,我可以使用Hamcrest的containsInAnyOrder来匹配它们的属性吗?

class Something{
int id;
int name;
}
class GsonSomething{
int id;
int name;
}

我可以使用containsInAnyOrder或任何其他方法来比较两个类中的两个列表吗?

List<Something> somethingList; //[ {id:1, name: "one"}, {id:1, name: "two"} ]

List<GsonSomething> gsonSomethingList; //[ {id:1, name: "one"}, {id:1, name: "two"} ]

此用例将用于测试方法中,以将结果与预期结果进行比较。我使用了以下方法:

for(Something s : somethingList){
GsonSomething gs = gsonSomethingList.stream().filter(p -> p.id.equals(s.id)).findFirst();
assertEquals(s.name, gs.name);
//assert other attributes
}

通过扩展http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/BaseMatcher.html并在断言中使用它。

不直接,不

为什么不呢

Java认为类型名称空间是神圣的,其他一切都只与它所在的类型有关。换句话说,在Java中,这两种方法:

class Gun() { void shoot(Person p); }
class Camera() { void shoot(Person p); }

被认为是完全不相关的,恰好具有相同名称的字段也是如此:这并不意味着它们是相关的。

好吧,我该怎么做

你将不得不竭尽全力将你的输入转换为可以比较的东西。

幸运的是,这相对简单。如果它是您想要比较的单个属性(比如int id值(,那么只需将两个值转换为该属性,然后运行比较。

如果你有"复合键"(你希望idname都相等(,你需要一些可以容纳这些键的类型。假设GsonSomethingSomething本身都符合可以完成这项工作的类型,所以您只需要将GsonSomething列表转换为Something列表,然后进行比较。

但是我的解决方案呢

您的解决方案有两个问题。

显而易见的是:它做得不好。如果gsonSomethingList中有一个元素根本没有出现在somethingList中,那么您的测试仍然会通过,这是不正确的。

问题稍微少一点:速度非常慢;它是O(n^2(速度。如果输入开始达到5位数(10k个输入,大约在这个范围内(,你会注意到的。一旦他们达到7分,这个测试就开始花费非常长的时间。

那么,还有什么更好的方法呢

为了加快比较速度,这些需要在一个集合中,这需要适当的equals和hashcode impls。让我们从那里开始,并使Something是记录类:

@lombok.EqualsAndHashCode
class Something {
int id;
int name;
}

如果lombok不是你正在使用的东西,也许:

class Something {
int id;
int name;
@Override public boolean equals(Object other) {
if (other == this) return true;
if (other == null) return false;
if (other.getClass() != Something.class) return false;
Something o = (Something) other;
return o.id == this.id && o.name == this.name;
// NB: Use .equals() and add null checks if `name` is String!
}
@Override public int hashCode() {
return (31 + id) * 31 + name;
}
}

现在我们有了它,我们可以将一个转换为另一个,并使用集合,这可以更快地完成这样的工作(事实上,在算法上也是如此(:

Set<Something> a = ....;
List<GsonSomething> rawB = ...;
Set<Something> b = rawB.stream()
.map(b -> new Something(b.getId(), b.getName())
.collect(Collectors.asSet());

现在您可以将containsInAnyOrder与ab一起使用。

我会做一些轻微的重构。我会首先创建一个基类,使SomethingGsonSomething类对其进行扩展。在基类中,我会覆盖equals方法。

abstract class BaseSomething {
int id;
String name; //This should definitely be a String type
//getters and setters
@Override
public boolean equals(Object other) {
if (other == this) return true;
if (other == null) return false;
if (!(other instanceof BaseSomething)) return false;
BaseSomething o = (BaseSomething) other;
return o.id == this.id && o.name.equals(this.name);
}
}
class Something extends BaseSomething {
}
class GsonSomething extends BaseSomething {
}

现在,这很容易。您的两个List字段现在将是:

List<BaseSomething> somethingList; //[ {id:1, name: "one"}, {id:1, name: "two"} ]

List<BaseSomething> gsonSomethingList; //[ {id:1, name: "one"}, {id:1, name: "two"} ]

对Hamcrest方法的简单调用就可以完成任务:

assertThat(gsonSomethingList, containsInAnyOrder(somethingList.toArray(new BaseSomething[0]));

最新更新