比较两组不同类型

Compare two sets of different types

我正在寻找一种方法来判断两组不同的元素类型是否相同,前提是我可以说明这些元素类型之间的一对一关系。在 java 或 guava 或 apache commons 中是否有执行此操作的标准方法?

这是我自己实现的这个任务。例如,我有两个元素 类 我知道如何比较。为了简单起见,我按id字段比较:

class ValueObject {
    public int id;
    public ValueObject(int id) { this.id=id; }
    public static ValueObject of(int id) { return new ValueObject(id); }
}

class DTO {
    public int id;
    public DTO(int id) { this.id=id; }
    public static DTO of(int id) { return new DTO(id); }
}

然后我定义一个接口来做比较

interface TwoTypesComparator<L,R> {
    boolean areIdentical(L left, R right);
}

比较集合的实际方法如下所示

public static <L,R> boolean areIdentical(Set<L> left, Set<R> right, TwoTypesComparator<L,R> comparator) {
    if (left.size() != right.size()) return false;
    boolean found;
    for (L l : left) {
        found = false;
        for (R r : right) {
            if (comparator.areIdentical(l, r)) {
                found = true; break;
            }
        }
        if (!found) return false;
    }
    return true;
}

客户代码示例

HashSet<ValueObject> valueObjects = new HashSet<ValueObject>();
valueObjects.add(ValueObject.of(1));
valueObjects.add(ValueObject.of(2));
valueObjects.add(ValueObject.of(3));

HashSet<DTO> dtos = new HashSet<DTO>();
dtos.add(DTO.of(1));
dtos.add(DTO.of(2));
dtos.add(DTO.of(34));

System.out.println(areIdentical(valueObjects, dtos, new TwoTypesComparator<ValueObject, DTO>() {
    @Override
    public boolean areIdentical(ValueObject left, DTO right) {
        return left.id == right.id;
    }
}));

我正在寻找此任务的标准解决方案。欢迎提出任何改进此代码的建议。

您可以覆盖 dto/value 对象上的 equals 和哈希码,然后执行:leftSet.containsAll(rightSet) && leftSet.size().equals(rightSet.size())

如果您无法更改元素 类,请创建一个装饰器并将集合设置为装饰器类型。

另一种解决方案是使用 List 而不是 Set(如果允许的话)。 List 有一个名为 get(int index) 的方法,可以检索指定索引处的元素,当两个列表的大小相同时,您可以将它们一一比较。有关列表的更多信息:http://docs.oracle.com/javase/7/docs/api/java/util/List.html

此外,避免在 类 中使用 public 变量。一个好的做法是将变量设为私有并使用 getter 和 setter 方法。

实例化列表并添加值

    List<ValueObject> list = new ArrayList<>();
    List<DTO> list2 = new ArrayList<>();

    list.add(ValueObject.of(1));
    list.add(ValueObject.of(2));
    list.add(ValueObject.of(3));

    list2.add(DTO.of(1));
    list2.add(DTO.of(2));
    list2.add(DTO.of(34));

比较列表的方法

public boolean compareLists(List<ValueObject> list, List<DTO> list2) {
    if(list.size() != list2.size()) {
        return false;
    }
    for(int i = 0; i < list.size(); i++) {
        if(list.get(i).id == list2.get(i).id) {
            continue;
        } else {
            return false;
        }
    }
    return true;
}

这就是我对你的情况所做的。你有套。集合很难比较,但最重要的是,您想比较它们的 id。

我只看到一个合适的解决方案,您必须规范化所需的值(提取它们的 ID)然后对这些 ID 进行排序,然后按顺序比较它们,因为如果您不这样做如果不进行排序和比较,您可以跳过重复的 and/or 个值。

想一想 Java 8 允许您懒惰地玩流。所以不要急着过来认为提取,然后排序然后复制很长。与迭代解决方案相比,惰性使其变得相当快。

HashSet<ValueObject> valueObjects = new HashSet<>();
valueObjects.add(ValueObject.of(1));
valueObjects.add(ValueObject.of(2));
valueObjects.add(ValueObject.of(3));

HashSet<DTO> dtos = new HashSet<>();
dtos.add(DTO.of(1));
dtos.add(DTO.of(2));
dtos.add(DTO.of(34));

boolean areIdentical = Arrays.equals(
    valueObjects.stream()
        .mapToInt((v) -> v.id)
        .sorted()
        .toArray(),
    dtos.stream()
        .mapToInt((d) -> d.id)
        .sorted()
        .toArray()
);

您想推广解决方案吗?没问题。

public static <T extends Comparable<?>> boolean areIdentical(Collection<ValueObject> vos, Function<ValueObject, T> voKeyExtractor, Collection<DTO> dtos, Function<DTO, T> dtoKeyExtractor) {
  return Arrays.equals(
    vos.stream()
      .map(voKeyExtractor)
      .sorted()
      .toArray(),
    dtos.stream()
      .map(dtoKeyExtractor)
      .sorted()
      .toArray()
  );
}

对于无法比较的 T

public static <T> boolean areIdentical(Collection<ValueObject> vos, Function<ValueObject, T> voKeyExtractor, Collection<DTO> dtos, Function<DTO, T> dtoKeyExtractor, Comparator<T> comparator) {
  return Arrays.equals(
    vos.stream()
      .map(voKeyExtractor)
      .sorted(comparator)
      .toArray(),
    dtos.stream()
      .map(dtoKeyExtractor)
      .sorted(comparator)
      .toArray()
  );
}

你提到番石榴,如果你没有 Java 8,你可以使用相同的算法执行以下操作:

List<Integer> voIds = FluentIterables.from(valueObjects)
  .transform(valueObjectIdGetter())
  .toSortedList(intComparator());
List<Integer> dtoIds = FluentIterables.from(dtos)
  .transform(dtoIdGetter())
  .toSortedList(intComparator());
return voIds.equals(dtoIds);

您当前的方法不正确,或者至少对于一般集合而言是不一致的。

想象一下:

L 包含对 (1,1)、(1,2)、(2,1)。

R 包含对 (1,1), (2,1), (2,2).

现在,如果您的 id 是第一个值,您的比较将 return 为真,但这些集合真的相等吗?问题是你不能保证集合中最多有一个具有相同 id 的元素,因为你不知道 LR 是如何实现相等的,所以我的建议是不要比较不同类型的集合。

如果你真的需要按照你描述的方式比较两个集合,我会把所有元素从 L 复制到一个列表,然后遍历 R,每次你在LList 中删除它。只要确保您使用 LinkedList 而不是 ArrayList .