簡體   English   中英

如何比較具有相似屬性的不同對象的 2 個列表

[英]How to compare 2 lists of different objects with similar properties

如果我有來自如下 2 個不同類的 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: "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); }

被認為是完全不相關的,對於碰巧具有相同名稱的字段也是如此:根本不意味着它們是相關的。

好的,那我該怎么做呢?

您將不得不 go 將您的輸入轉換為可以比較的東西。

幸運的是,這相對簡單。 如果它是您要比較的單個屬性(例如, int id值),則只需將兩個值都轉換為該值,然后運行比較。

如果你有“復合鍵”(你希望idname都相等),你需要一些可以保存這些鍵的類型。 大概GsonSomethingSomething本身都是可以完成這項工作的類型,因此您實際上只需要將GsonSomething列表轉換為Something列表,然后您就可以進行比較。

但是我的解決方案呢

您的解決方案有兩個問題。

最明顯的是:它沒有做好正確的工作。 如果 gsonSomethingList 中有一個元素根本沒有出現在 somethingList 中,你的測試仍然會通過,這是不正確的。

問題稍微少一點:它非常慢; 這是 O(n^2) 速度。 如果輸入開始達到 5 位數(10k 條目及以上 - 在那個范圍內),您會注意到。 一旦他們達到 7,這個測試就開始花費非常長的時間。

那么什么是更好的方法呢?

為了加速比較,這些需要在一個集合中,這需要適當的 equals 和 hashcode impls。 那么讓我們從這里開始,讓Something are class of record:

@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一起使用。

我會做一些輕微的重構。 我會首先創建一個基礎 class,使SomethingGsonSomething class 擴展它。 在基礎 class 中,我將覆蓋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]));

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM