先来看下面一个例子:先定义了一个简单的POJO类,然后实例化了一个ArrayList存储Student对象
import java.util.ArrayList;
import java.util.List;
class Student{
public int id;
public String name;
public Student(int id,String name) {
this.id=id;
this.name=name;
}
@Override
public String toString() {
return "id : " + id + " name : " + name;
}
}
public class TestCollection {
public static void main(String[] args) {
List<Student> stu = new ArrayList<>();
stu.add(new Student(1, "andy"));
stu.add(new Student(2, "john"));
stu.add(new Student(3, "jack"));
}
}
进行如下操作:
Student black=new Student(4, "black");
stu.add(black);
System.out.println(stu.contains(black));//判断stu中是否包含black对象
打印结果:true
没有问题
再进行如下操作:
System.out.println(stu.contains(new Student(3, "jack")));
打印结果:false
打印出来为flase,但是我们的stu中确实有{ id=3,name="jack"}的对象,仔细一想也没什么问题,new Student(3, "jack") 创建的是一个{ id=3,name="jack"}的匿名对象,前面stu.add(new Student(3, "jack")) 和stu.contains(new Student(3, "jack"))中的{ id=3,name="jack"}本来就不是同一块地址空间,显然这不是我们想看到的。
再进行如下操作
stu.remove(new Student(1,"andy"));
System.out.println(stu.get(0));
打印结果:id : 1 name : andy
显然没有将第一个信息删除,至于原因和上面是相似的,那么怎么解决这个问题呢?
如果List需要存储引用类型,并且使用到 .remove() , contains() 等方法,则需要该引用类型复写 .equals() 方法。
以下为复写 .equals() 的完整Student 实例
import java.util.ArrayList;
import java.util.List;
class Student {
public int id;
public String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "id : " + id + " name : " + name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;//如果传进来的对象就是当前对象就直接返回true
}
if (obj instanceof Student) {
Student stu = (Student) obj;
if (this.id == stu.id) {
if (this.name.equals(stu.name)) {
return true;
}
return false;
}
return false;
}
return false;
}
}
public class TestCollection {
public static void main(String[] args) {
List<Student> stu = new ArrayList<>();
stu.add(new Student(1, "andy"));
stu.add(new Student(2, "john"));
stu.add(new Student(3, "jack"));
Student black = new Student(4, "black");
stu.add(black);
System.out.println("stu是否包含这个对象:"+stu.contains(new Student(3, "jack")));
stu.remove(new Student(1, "andy"));
System.out.println(stu.get(0));//id=1 的 andy 被成功删除
}
}
打印结果:
stu是否包含这个对象:true
id : 2 name : john
这正是我们想看到的结果,下面来探究一下,为什么会这样。
首先打开ArrayList中的 .remove() 相关源码:
首先他传入了一个Object 对象,在我们这个程序中,传入的对象为:new Student(1, "andy"),
o !=null; 程序跳转到551行
552行:for循环遍历这个集合中的数据
553行:有一个判断条件 o.equals(elementData[index] ;
正是调用了我们复写的 .equals(); 当 .equals()返回true时则将集合中的这个数据删除。
再打开ArrayList中的 .contains() 相关源码:
可以看到,他在判断这个对象是否存在于这个集合中时,都会调用 .equals()方法。
那么为什么会出现这个现象呢?
不难理解:Java中,任何类都继承了Object方法,而 .equals() 方法最初被定义在Object中,当你没有复写他时,会自动调用Object中的 .equals() 。
那么我们打开Object中的 .equals():
非常简单:只要不是这个对象,就返回false。前面的打印结果就很好理解了。
我们来总结一下:当你调用 集合类中的 .remove(o) 方法会经历怎样一个过程,当这个对象不为空的时候,他会用一个for循环遍历整个集合,再调用o.equals()与集合中每个元素进行比较。如果 .equals() 被复写了,就会调用这个类中的.equals(),如果没有复写,就会调用Object中的 .equals()。当.equals() 返回true时,就将集合中的这个元素删除。
以下方法也会调用到 .equals();
在LinkedList 和 Vector 中的相关方法,也会调用 .equals(), 因此List需要存储引用类型,并且使用到 .remove() , contains() 等方法,则需要该引用类型复写 .equals() 方法。