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

snvhrwxg  于 2021-07-12  发布在  Java
关注(0)|答案(3)|浏览(325)

如果我有两个来自如下两个不同类的示例(具有相似的属性),我可以使用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

}
r55awzrz

r55awzrz1#

通过扩展http://hamcrest.org/javahamcrest/javadoc/1.3/org/hamcrest/basematcher.html 并在Assert中使用它。

pvabu6sv

pvabu6sv2#

不是直接的,不是。

为什么不呢?

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 impl。让我们从这里开始 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 .

tvmytwxo

tvmytwxo3#

我会做一些轻微的重构。我首先创建一个基类 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]));

相关问题