contains()方法是我们用于判断一个集合中是否含有某一元素的方法,那么作为ArrayList.util所提供的方法,它又是怎样实现的呢?本篇博客将深入contains()方法的底层代码,探究其实现原理。
情形一:参数存在
实例
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList<String> nameList = new ArrayList();
nameList.add("Tom");
String name = "Tom";
System.out.println(namelist.contains(name));
}
}
上面代码段中,我们创建了集合nameList并添加了一个元素“Tom”,然后定义了一个字符串“Tom”.接下来,我们使用“Ctrl+单击contains方法”,打开contains所在的类,分析底层代码(见代码注释):
底层代码
public boolean contains(Object o) {//name为传入参数,o指向name对象,name为上转型对象
return indexOf(o) >= 0;//调用下面的indexOf方法
}
public int indexOf(Object o) {//o同样指向name
if (o == null) {//name指向的字符串不为null,if语句不执行
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)//在编译器中可看出,size是全局变量,其前面隐藏this(其实为i < this.size)用于获得集合nameList含有元素的个数
if (o.equals(elementData[i]))//调用String类型中的equals方法将集合中的每个元素逐个与name相比是否相等
return i;//相等则返回大于或等于零的值给上面contains方法最终返回true,判断结束
}
return -1;
}
情形二:参数不存在
实例
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList<String> nameList = new ArrayList();
nameList.add("Tom");
String name = null;
System.out.println(namelist.contains(name));
}
}
这种情形下,name字符串为null,分析底层代码如下(见于代码注释):
底层代码
public boolean contains(Object o) {//name为传入参数,o指向name对象,name为上转型对象
return indexOf(o) >= 0;//调用下面的indexOf方法
}
public int indexOf(Object o) {//o同样指向name
if (o == null) {//name指向的字符串为null,执行if语句
for (int i = 0; i < size; i++)//遍历集合中所有元素
if (elementData[i]==null)//判断是否存在元素为null
return i;//相等则返回大于或等于零的值给上面contains方法最终返回true,判断结束
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
注:使用equals()方法判断两对象是否相等的原理可参考:分析equals()方法底层代码
情形三:类型不同
实例
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList<String> nameList = new ArrayList();
nameList.add("Tom");
Test test = new Test();
System.out.println(namelist.contains(test));
}
}
这种情况下我们定义了Test类型的test对象,若仍要判断泛型为String的集合是否包含test,结果是false,下面分析底层代码探究原理(见于代码注释):
底层代码
public boolean contains(Object o) {//test为传入参数,o指向test对象,test为上转型对象
return indexOf(o) >= 0;//调用下面的indexOf方法
}
public int indexOf(Object o) {//o同样指向test
if (o == null) {//test地址不为空,不执行if语句
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)//遍历集合中所有元素判断是否存在元素与test相同
if (o.equals(elementData[i]))//使用equals方法判断是否相等,由于Test类没有重写equals方法,因此继承父类Object的equals方法(对比地址)
return i;
}
return -1;//不相等,则返回-1值上面contains方法最终返回false,判断结束
}
情形四:判断自定义类型
实例
首先定义一个Student类并且定义其含参构造方法
public class Student {
private String id;
private String name;
public Student(String id, String name) {
this.id = id;
this.name = name;
}
}
接下来我们创建Student类型对象,并向ArrayList集合中添加Student类型元素
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList<Student> nameList = new ArrayList();
nameList.add(new Student("001","Tom"));//向ArrayList集合中添加Student类型元素
Student student = new Student("001","Jim");//创建Student类型对象
System.out.println(namelist.contains(student));
}
}
底层代码
public boolean contains(Object o) {//student为传入参数,o指向student对象,student为上转型对象
return indexOf(o) >= 0;//调用下面的indexOf方法
}
public int indexOf(Object o) {//o同样指向student
if (o == null) {//student地址不为空,不执行if语句
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]))//o指向的对象student与nameList遍历出来的元素对比,由于Student没有重写equals()方法,所以用的是继承父类Object的equals方法(对比地址)
return i;
}
return -1;//不相等,则返回-1值上面contains方法最终返回false,判断结束
}
思考
在上面“自定义类型”的实例中,即使创建的Student类型对象和向ArrayList集合中添加的Student类型元素字符串数值全部相同,调用contains方法的最终结果仍然是false,原因就是因为,上面student对象和studentList元素的数据类型都是我们自定义的Student类型,因此在contains底层代码调用equals方法并非是我们常用的String类当中的equals方法,而是Student类型继承父类Object中的equals方法,而Object中的equals方法中仅仅判断两对象地址是否相等!
那么,如果在Student类中重写equals方法,那么就可以达到自定义判断的效果,改动如下:
public class Student {
private String id;
private String name;
public Student(String id, String name) {
this.id = id;
this.name = name;
}
// @Override
// public boolean equals(Object obj) {
// return super.equals(obj);
// }
//上面是默认继承Object的equals方法
@Override
public boolean equals(Object obj) {
Student student = (Student)obj;//将我们前面用的元素(作为参数传递给Object类型的变量obj称为上转型)下转型
return this.id.equals(student.id);//this代表调用这个方法的对象o(指向name);id是String类型,调用String类型中的equals方法判断是否与元素的id相同
}
}
上面代码通过重写equals()方法实现了比较自定义类Student的id属性,当然我们也可以重写equals使其达到比较Student类的name属性。