英文:
How to compare 2 lists of different objects with similar properties
问题
如果我有来自两个不同类别的两个实例,如下所示(具有类似的属性),我可以使用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
}
英文:
If I have 2 instances from 2 different classes as follows (with similar attributes), can I use Hamcrest's containsInAnyOrder
to match their attributes?
class Something{
int id;
int name;
}
class GsonSomething{
int id;
int name;
}
Can I use containsInAnyOrder
or any other method to compare 2 lists from the 2 classes?
List<Something> somethingList; //[ {id:1, name: "one"}, {id:1, name: "two"} ]
List<GsonSomething> gsonSomethingList; //[ {id:1, name: "one"}, {id:1, name: "two"} ]
The use case of this is to be used in a test method to compare the result vs the expected. And I used the following approach:
for(Something s : somethingList){
GsonSomething gs = gsonSomethingList.stream().filter(p -> p.id.equals(s.id)).findFirst();
assertEquals(s.name, gs.name);
//assert other attributes
}
答案1
得分: 3
通过扩展 http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/BaseMatcher.html 来创建您自己的匹配器,并在断言中使用它。
英文:
Create your own matcher by extending http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/BaseMatcher.html and use it in the assertion.
答案2
得分: 2
不直接地进行比较。
为什么不行呢?
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 位数(1 万个条目或更多——在那个数量级上),你会注意到性能下降。当元素数量达到 7 位数时,这个测试将变得非常耗时。
那有更好的方法吗?
为了加速比较过程,这些元素需要在一个集合中,这就需要正确的 equals 和 hashcode 实现。所以,让我们从这里开始,将 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;
// 注意:如果 `name` 是 String,要使用 .equals() 并添加空值检查!
}
@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.toSet());
然后你可以使用 containsInAnyOrder
来比较 a
和 b
。
英文:
Not directly, no.
Why not?
Java holds that the type namespace is holy, and everything else is only relative to the type it is in. In other words, in java, these two methods:
class Gun() { void shoot(Person p); }
class Camera() { void shoot(Person p); }
are considered to be utterly unrelated, and the same goes for fields that so happen to have the same name: Does not mean they are related, at all.
Okay, so how do I do this?
You're going to have to go out of your way to convert your inputs to something that can be compared.
Fortunately, that is relatively simple. Had it been a single property you want to compare (say, the int id
value), then just convert both values to that and then run your comparison.
If you have 'compound keys' (you want both id
and name
to be equal) you need some type that can hold these keys. Presumably both GsonSomething
and Something
are themselves qualifying as types that can do the job, so you really just need to convert, say, your list of GsonSomething
to a list of Something
and then you can compare.
But what about my solution
Your solution has two problems.
The glaring one: It's not doing a proper job. If there is an element in gsonSomethingList that does not appear in somethingList at all, your test will still pass, which would be incorrect.
A slightly less problematic one: It is very slow; it's O(n^2) speed. If the inputs start hitting 5 digits (10k entries and up - in that ballpark), you're going to notice. Once they hit 7, this test starts taking extremely long.
So what is a better way?
To speed up the comparison, these need to be in a set, which requires proper equals and hashcode impls. So let's start there, and make Something
are class of record:
@lombok.EqualsAndHashCode
class Something {
int id;
int name;
}
If lombok is not something you're using, perhaps:
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;
}
}
Now that we have that, we can convert one to the other, and use sets, which can do such a job much faster (algorithmically so, in fact):
Set<Something> a = ....;
List<GsonSomething> rawB = ...;
Set<Something> b = rawB.stream()
.map(b -> new Something(b.getId(), b.getName())
.collect(Collectors.asSet());
and now you can use e.g. containsInAnyOrder with a
and b
.
答案3
得分: 1
我会做一些轻微的重构。首先,我会创建一个基类,使 Something
和 GsonSomething
类继承它。在基类中,我会重写 equals
方法。
abstract class BaseSomething {
int id;
String name; // 这应该明确地是一个 String 类型
// 获取器和设置器
@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])));
英文:
I would do some slight refactoring. I would first create a base class, make Something
and GsonSomething
class extend it. In the base class I would override the equals
method.
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 {
}
Now, this are pretty easy. Your two List
fields will be now:
List<BaseSomething> somethingList; //[ {id:1, name: "one"}, {id:1, name: "two"} ]
List<BaseSomething> gsonSomethingList; //[ {id:1, name: "one"}, {id:1, name: "two"} ]
And a simple call to Hamcrest method should do the job:
assertThat(gsonSomethingList, containsInAnyOrder(somethingList.toArray(new BaseSomething[0]));
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论