Java编程离不开容器,使用容器又会常常用到contains()方法,这篇文章通过极其详细的测试,探究了 java 中 contains() 方法实现的底层原理。
第一波测试
- String 和自定义 object 类之间储存方式的区别
- String 和 object在使用 contains 时的差别
- List Set Map 的不同表现
//注释为false的是错误,没写的是true
import java.util.*;
import javax.print.attribute.HashAttributeSet;
public class contains{
public static void main(String[] args){
String str1 = "aaa";
String str11 = "aaa";
String str2 = "bbb";
//string 存储方式,重复字符串不会另行开辟空间
System.out.println("String storage test:");
System.out.println(str1 == "aaa");
System.out.println(str1 == str11);
System.out.println(str1.equals(str2));// false
System.out.println(str1.equals("aaa"));
Person p1 = new Person("A", 1);
Person p11 = new Person("A", 1);
Person p2 = new Person("B", 2);
//new几个就有几个空间
System.out.println("\nObject storage test");
System.out.println(p1 == p11); // false
System.out.println(p1.equals(p11));// false
//list string
List<String> list1 = new ArrayList<>();
list1.add(str1);
System.out.println("\nList String test:");
System.out.println(list1.contains(str1));
System.out.println(list1.contains("aaa"));
System.out.println(list1.contains(str11));
//list object
List<Person> list2 = new ArrayList<>();
list2.add(p1);
System.out.println("\nList object test:");
System.out.println(list2.contains(p1));
System.out.println(list2.contains(p11));// false
System.out.println(list2.contains(p2));// false
//set string
Set<String> set1 = new HashSet<String>();
set1.add(str1);
System.out.println("\nSet String test:");
System.out.println(set1.contains(str1));
System.out.println(set1.contains("aaa"));
System.out.println(set1.contains(str11));
//set object
Set<Person> set2 = new HashSet<Person>();
set2.add(p1);
System.out.println("\nSet Object test:");
System.out.println(set2.contains(p1));
System.out.println(set2.contains(p11));// false
System.out.println(set2.contains(p2));// false
//map string
Map<String,String> map1 = new HashMap<String, String>();
map1.put(str1, str1);
System.out.println("\nMap String test:");
System.out.println(map1.containsKey(str1));
System.out.println(map1.containsKey(str11));
System.out.println(map1.containsValue(str1));
System.out.println(map1.containsValue(str11));
//map object
Map<Person,Person> map2 = new HashMap<Person,Person>();
map2.put(p1,p1);
System.out.println("\nMap Object test:");
System.out.println(map2.containsKey(p1));
System.out.println(map2.containsKey(p11));// false
System.out.println(map2.containsValue(p1));
System.out.println(map2.containsValue(p11));// false
}
}
class Person{
private String name;
private int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
public String getName(){
return this.name;
}
public int getAge(){
return this.age;
}
}
可以看出,String 只要内容相同就被判断为 true
第二波测试
重写Person的equals方法之后的结果:
//注释为false的是错误,没写的是true
import java.util.*;
import javax.print.attribute.HashAttributeSet;
public class contains{
public static void main(String[] args){
String str1 = "aaa";
String str11 = "aaa";
String str2 = "bbb";
//string 存储方式,重复字符串不会另行开辟空间
System.out.println("String storage test:");
System.out.println(str1 == "aaa");
System.out.println(str1 == str11);
System.out.println(str1.equals(str2));// false
System.out.println(str1.equals("aaa"));
Person p1 = new Person("A", 1);
Person p11 = new Person("A", 1);
Person p2 = new Person("B", 2);
//new几个就有几个空间
System.out.println("\nObject storage test");
System.out.println(p1 == p11); // false
System.out.println(p1.equals(p11));
//list string
List<String> list1 = new ArrayList<>();
list1.add(str1);
System.out.println("\nList String test:");
System.out.println(list1.contains(str1));
System.out.println(list1.contains("aaa"));
System.out.println(list1.contains(str11));
//list object
List<Person> list2 = new ArrayList<>();
list2.add(p1);
System.out.println("\nList object test:");
System.out.println(list2.contains(p1));
System.out.println(list2.contains(p11));
System.out.println(list2.contains(p2));// false
//set string
Set<String> set1 = new HashSet<String>();
set1.add(str1);
System.out.println("\nSet String test:");
System.out.println(set1.contains(str1));
System.out.println(set1.contains("aaa"));
System.out.println(set1.contains(str11));
//set object
Set<Person> set2 = new HashSet<Person>();
set2.add(p1);
System.out.println("\nSet Object test:");
System.out.println(set2.contains(p1));
System.out.println(set2.contains(p11));// false
System.out.println(set2.contains(p2));// false
//map string
Map<String,String> map1 = new HashMap<String, String>();
map1.put(str1, str1);
System.out.println("\nMap String test:");
System.out.println(map1.containsKey(str1));
System.out.println(map1.containsKey(str11));
System.out.println(map1.containsValue(str1));
System.out.println(map1.containsValue(str11));
//map object
Map<Person,Person> map2 = new HashMap<Person,Person>();
map2.put(p1,p1);
System.out.println("\nMap Object test:");
System.out.println(map2.containsKey(p1));
System.out.println(map2.containsKey(p11));// false
System.out.println(map2.containsValue(p1));
System.out.println(map2.containsValue(p11));
}
}
class Person{
private String name;
private int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
public String getName(){
return this.name;
}
public int getAge(){
return this.age;
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Person){
if (this.getName().equals(((Person)obj).getName())
&&(this.getAge() == ((Person)obj).getAge())){
return true;
}
}
return false;
}
}
不出所料的是,不同的拥有相同内容的Person可以equal,list 的contains 能够识别内容相同的 Person,然而 set 却没有变化 ,map 的 containsValue 能够识别内容相同的 Person,而containsKey 却不能。
这是一个很有趣的结果
原因是,set 和 map 的 containskey 在使用equals的同时,也会使用 hashCode() 方法,因此,两个方法返回都相同时,两个对象才能被视为相等。
第三波测试
重写hashCode() 方法:
//注释为false的是错误,没写的是true
import java.util.*;
import javax.print.attribute.HashAttributeSet;
public class contains{
public static void main(String[] args){
String str1 = "aaa";
String str11 = "aaa";
String str2 = "bbb";
//string 存储方式,重复字符串不会另行开辟空间
System.out.println("String storage test:");
System.out.println(str1 == "aaa");
System.out.println(str1 == str11);
System.out.println(str1.equals(str2));// false
System.out.println(str1.equals("aaa"));
Person p1 = new Person("A", 1);
Person p11 = new Person("A", 1);
Person p2 = new Person("B", 2);
//new几个就有几个空间
System.out.println("\nObject storage test");
System.out.println(p1 == p11); // false
System.out.println(p1.equals(p11));
//list string
List<String> list1 = new ArrayList<>();
list1.add(str1);
System.out.println("\nList String test:");
System.out.println(list1.contains(str1));
System.out.println(list1.contains("aaa"));
System.out.println(list1.contains(str11));
//list object
List<Person> list2 = new ArrayList<>();
list2.add(p1);
System.out.println("\nList object test:");
System.out.println(list2.contains(p1));
System.out.println(list2.contains(p11));
System.out.println(list2.contains(p2));// false
//set string
Set<String> set1 = new HashSet<String>();
set1.add(str1);
System.out.println("\nSet String test:");
System.out.println(set1.contains(str1));
System.out.println(set1.contains("aaa"));
System.out.println(set1.contains(str11));
//set object
Set<Person> set2 = new HashSet<Person>();
set2.add(p1);
System.out.println("\nSet Object test:");
System.out.println(set2.contains(p1));
System.out.println(set2.contains(p11));
System.out.println(set2.contains(p2));// false
//map string
Map<String,String> map1 = new HashMap<String, String>();
map1.put(str1, str1);
System.out.println("\nMap String test:");
System.out.println(map1.containsKey(str1));
System.out.println(map1.containsKey(str11));
System.out.println(map1.containsValue(str1));
System.out.println(map1.containsValue(str11));
//map object
Map<Person,Person> map2 = new HashMap<Person,Person>();
map2.put(p1,p1);
System.out.println("\nMap Object test:");
System.out.println(map2.containsKey(p1));
System.out.println(map2.containsKey(p11));
System.out.println(map2.containsValue(p1));
System.out.println(map2.containsValue(p11));
}
}
class Person{
private String name;
private int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
public String getName(){
return this.name;
}
public int getAge(){
return this.age;
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Person){
if (this.getName().equals(((Person)obj).getName())
&&(this.getAge() == ((Person)obj).getAge())){
return true;
}
}
return false;
}
@Override
public int hashCode() {
return this.getAge();
}
}
结论:set 的contains 和 map 的 containskey 在使用equals的同时,也会先使用 hashCode() 方法,在hashCode 相同的基础上再通过equals 判断是否相同。两个判断都相同时,才认定元素相等,contains 返回 true
后话:之后的学习中也印证了,set 的 contains 使用hashcode 方法判断等价性是因为,上面例子中使用的是 hashset ,也就是,set 的下层实现方式是哈希,所以才会跟 hashcode 扯上关系。同理,map 也因为 hashmap 底层的哈希原理。