在Java中,相同类型但不同类型参数的空集合是否总是相等的?

In Java, will empty collections of the same type, but different type parameters always be equal?

考虑以下片段:

    List<String> list1 = new ArrayList<>();
    List<Integer> list2 = new ArrayList<>();
    assertThat(list1.equals(list2), is(true));

断言成功。

事实上,据我了解,Java 中无法通过 equals 方法区分两者,因为 运行-time type-erasure (即类型参数 StringInteger 只能在编译时访问,但不能在 运行 时访问)。 由于没有可以比较的元素,equals 必须 return 为真。按照这种思路,对于所有集合来说,这一定是真的。

所以问题如下:我的思维过程是正确的,还是我遗漏了什么?

编辑

@qqilihq 给了一个很好的,提示下一个问题: 这是否可以在不显式 传递 类型的情况下实现(如果类型以某种方式显式 stored 我很好,只是用户不应该有通过它。)

我尝试了以下操作,但没有成功(我猜是由于类型擦除):

    public TypedList(List<T> delegate) {
        this.delegate = Objects.requireNonNull(delegate);
        this.type = (Class<T>) delegate.getClass();
    }

但也许你可以做类似的事情? this.type = T。 (这不会编译,但也许类似的东西是可能的。)

这实际上取决于实现集合接口的各个子类如何处理 equals() 方法。对于 ArrayList,实现在 AbstractList 中,如下所示:

public boolean equals(Object o) {
    if (o == this)
        return true;
    if (!(o instanceof List))
        return false;

    ListIterator<E> e1 = listIterator();
    ListIterator<?> e2 = ((List<?>) o).listIterator();
    while (e1.hasNext() && e2.hasNext()) {
        E o1 = e1.next();
        Object o2 = e2.next();
        if (!(o1==null ? o2==null : o1.equals(o2)))
            return false;
    }
    return !(e1.hasNext() || e2.hasNext());
}

所以如果对面的对象也是一个 List 并且大小相同并且每个元素都相等,那么它 return 为真。因此,如果两个 List 都为空,则结果必须为真。

如您所述,在您给出的示例中,类型信息在运行时丢失。但是关于你在评论中的问题:

can you also show a counter-example, i.e. a collection where two empty instances of different generic types would not be treated as equal?

您当然可以实现自己的 Collection 会考虑 equals 方法中的类型信息。这是一个非常简单的示例,它围绕任意 List:

创建一个 TypedList 包装器
static final class TypedList<T> extends AbstractList<T> {
    private final List<T> delegate;
    private final Class<T> type;

    public TypedList(List<T> delegate, Class<T> type) {
        this.delegate = Objects.requireNonNull(delegate);
        this.type = Objects.requireNonNull(type);
    }

    @Override
    public T get(int index) {
        return delegate.get(index);
    }

    @Override
    public int size() {
        return delegate.size();
    }

    @Override
    public boolean equals(Object obj) {
        if (!super.equals(obj)) {
            return false;
        }
        // Lists are equal, now additionally, check the type
        TypedList<?> other = (TypedList<?>) obj;
        return this.type.equals(other.type);
    }

    // hashCode omitted for brevity
}

请注意,由于提到的类型擦除,您需要显式存储类型。用法:

List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.equals(list2)); // true

list1 = new TypedList<>(list1, String.class);
list2 = new TypedList<>(list2, Integer.class);
System.out.println(list1.equals(list2)); // false

Generics are used for Type Checking at compile time.

如果您这样创建 ArrayList

List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.equals(list2)); // result:true

list1list2 将相等,因为它们是空的。ArrayList 内部使用 Object[] 来存储 ArrayList 条目。

如果您向数组列表中添加一些条目:

List<String> list1 = new ArrayList<>();
list1.add("A");

List<Integer> list2 = new ArrayList<>();
list2.add(1);

System.out.println(list1.equals(list2)); // result: false

这将 return false 因为 ArrayList 条目不同