在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 (即类型参数 String
和 Integer
只能在编译时访问,但不能在 运行 时访问)。
由于没有可以比较的元素,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
list1
和 list2
将相等,因为它们是空的。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
条目不同
考虑以下片段:
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
assertThat(list1.equals(list2), is(true));
断言成功。
事实上,据我了解,Java 中无法通过 equals
方法区分两者,因为 运行-time type-erasure (即类型参数 String
和 Integer
只能在编译时访问,但不能在 运行 时访问)。
由于没有可以比较的元素,equals
必须 return 为真。按照这种思路,对于所有集合来说,这一定是真的。
所以问题如下:我的思维过程是正确的,还是我遗漏了什么?
编辑
@qqilihq 给了一个很好的
我尝试了以下操作,但没有成功(我猜是由于类型擦除):
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
list1
和 list2
将相等,因为它们是空的。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
条目不同