如果我有来自如下两个不同类的两个实例(具有相似的属性(,我可以使用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
值(,那么只需将两个值转换为该属性,然后运行比较。
如果你有"复合键"(你希望id
和name
都相等(,你需要一些可以容纳这些键的类型。假设GsonSomething
和Something
本身都符合可以完成这项工作的类型,所以您只需要将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与a
和b
一起使用。
我会做一些轻微的重构。我会首先创建一个基类,使Something
和GsonSomething
类对其进行扩展。在基类中,我会覆盖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]));