一、先说结论:
1、List(ArrayList)调用equals方法,判断的是存储的元素相等,而不是直接比较引用。说明List(ArrayList)重写了equals方法。
List<T> l1 = new ArrayList<>();
l1.add(T1);
List<T> l2 = new ArrayList<>();
l2.add(T1);
l1.equals(l2); //true
2、List<List< String > > res 中,可以对相同List< String>去重。直接使用res.contains(List)方法即可。
二、源码分析:
出于对ArrayList源码分析:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
发现ArrayList实现了List接口,按理来说ArrayList需要实现所有List的接口方法。但是equals方法可以从extends的Object类获得[1] 。所以我误认为ArrayList的equals方法是Object类的equals方法。ArrayList中元素需比较引用。
在对复合集合res添加List时,觉得在添加新的List时,需要遍历res所有的List,然后遍历所有List元素来去重。算法复杂度就变得很高。后来尝试发现直接contains即可?
List<List<String>> res = new ArrayList<>();
List<String> l1 = new ArrayList<>();
List<String> l2 = new ArrayList<>();
l1.add("123");
l2.add("123");
res.add(l1);
System.out.println(res.contains(l2));// true
(1)首先contains方法是调用indexOf方法确定存在性。
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
(2)indexOf方法是将List中所有元素,与Object o比较。
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
(3)最终还是需要将List中元素,与Object o做equals比较。(就是遍历res中的List与给定targetList进行equals比较)
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
这里调用的equals方法并不是Object类中的。ArrayList继承了AbstractList< E>,它的equals方法应该从这个抽象类中来的。发现该类确实重写了equals方法,就是使用迭代器遍历两个List每个元素是否相等。 那么ArrayList调用equals方法就是元素进行比较了。
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());
}
因此,得出文章开头的1、2两点结论。以上~
三、补充
[1] 这里补充为啥没有显式extends Object的原因分析:
这个继承关系是是编译阶段完成的,所以我们表面上看不到。
在编译阶段,当遇到一个类没有父类的使用,编译器会指定一个默认的父类(一般为Object),当该类已经有一个父类,jvm会按照常规的方法去处理每一个类。
//原始的类,没有显式的继承Object
public class Test{
//注意这里没有写构造方法
public static void main(String args[]){
}
}
将代码编译为class后反编译为txt文件
//编译后,如果没有父类会给你加一个默认的object父类
public class Test extends java.lang.Object{
public Test();//编译阶段,如果没有构造方法会给你加一个空的构造方法
public static void main(java.lang.String[]){};
}