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

问题描述

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

class Something{

    int id;
    int name;
}

class GsonSomething{

    int id;
    int name;
}

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

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


List<GsonSomething> gsonSomethingList; //[ {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的列表转换为{{1}的列表},然后您可以进行比较。

那我的解决方案怎么办

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

一个明显的人:它做得不好。如果gsonSomethingList中有一个元素根本没有出现在somethingList中,则您的测试仍将通过,这是不正确的。

一个不太麻烦的问题:它非常慢;这是O(n ^ 2)的速度。如果输入开始达到5位数字(输入1万以上),您会注意到。当他们达到7时,该测试将花费极长时间。

那有什么更好的方法呢?

要加快比较速度,必须将它们放在一组中,这需要适当的equals和哈希码impls。因此,让我们从此处开始,并使Something为记录类别:

Something

如果您不使用lombok,也许:

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

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

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()); a的containsInAnyOrder。

,

我会进行一些重构。我首先创建一个基类,使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: "two"} ]

只需调用Hamcrest方法即可完成工作:

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

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...