Java中 ==、equals、hashCode
==
==是java重载的一个双目运算符,比较的是左右两边对象在堆内存中的地址是否相等,也就是比较两边是不是同一个对象。
看下面一段程序
public static void main(String[] args) {
HashMap map1=new HashMap();
HashMap map2=new HashMap();
System.out.println(map1==map2);
int a=123;
int b=123;
System.out.println(a==b);
String s1="123";
String s2="123";
System.out.println(s1==s2);
Integer a1=1;
Integer b1=1;
System.out.println(a1==b1);
Integer a2=128;
Integer b2=128;
System.out.println(a2==b2);
}
输出结果
image.png
下面我们来分析一下。
1.我们new一个常规对象,其实例放在堆内存中,其引用方法栈内存中。每次new一个对象,都会在堆内存中重新分配一块内存并创建新的实例。所以,在
HashMap map1=new HashMap();
HashMap map2=new HashMap();
中,我们创建了两个实例对象,这两个实例对象保存在堆内存中,两个应用变量map1和map2保存在栈内存中main方法的栈帧的局部变量表中,map1和map2存放的值是实例在堆内存中的地址。因为map1和map2指向了不同的实例对象,所以map1和map2保存的值也不一样,所以System.out.println(map1==map2)输出为false。
2.我们又创建了两个int型的局部变量,值都是123。对于基本数据类型(byte,short,char,int,float,double,long,boolean)来说,他们是作为常量在方法区中的常量池里面以HashSet策略存储起来的,常量池中相同的变量只会有一个实例,因此a和b指向同一个地址,因此System.out.println(a==b)输出为true。
3.我们又创建了两个String类型的局部变量,值都是“123”.对于String,在方法区中有一个字符串常量池,字符串常量池中相同的字符串只会有一个实例,我们使用如下代码创建的两个局部变量。
String s1="123";
String s2="123";
两个引用s1和s2保存在栈中,“123”保存在字符串常量池中,因此s1和s2指向的是同一个字符串实例,因此System.out.println(s1==s2)输出true。
4.我们又使用下面的代码创建了两个Integer对象。
Integer a1=1;
Integer b1=1;
System.out.println(a1==b1);
对于基本数据的包装类型(Byte, Short, Character,Integer,Float, Double,Long, Boolean)除了Float和Double之外,其他的六种都是实现了常量池的。所以,a1和b1指向一样的地址,所以输出true。
5.我们使用下面的代码创建了两个Integer对象
Integer a2=128;
Integer b2=128;
System.out.println(a2==b2);
虽然Integer实现了常量池,但是 Integer 在常量池中的存储范围为[-128,127],127在这范围内,因此是直接存储于常量池的,而128不在这范围内,所以会在堆内存中创建一个新的对象来保存这个值,所以a2和b2指向了不同的地址,所以输出false。
所以,==就是比较前后两个变量的值,因为引用变量里面保存的是实例变量的地址,我们要尤其注意以下。
equals(Object o)
equals是object类中的一个public方法,因此java中所有的类都具有equals方法,只不过有些类重写了equals方法。首先我们看以下Object类中的equals方法。
public boolean equals(Object obj) {
return (this == obj);
}
我们看到,在Object类中,就是使用==比较两个对象。所以,如果一个类没有重写equals方法,那么使用他的equals方法和使用==比较的结果是一样的。
但是很多类对equals方法进行了重写。
如String类的equals方法
public boolean equals(Object anObject) {
if (this == anObject) { //首先使用==比较
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) { //比较两个String的值是否相等。
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
在上述方法中,首先使用==比较两个对象,如果相等直接返回true。然后在比较两个String对象保存的值是不是相等。
HashCode
hashCode是object类中的一个方法,我们查看该方法如下。
public native int hashCode();
在Object类中,该方法是一个native方法, 他的实现在native层中。很多类都对hashCode方法进行了重写。
如String方法中的hashCode
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
String方法的hashCode中,最终生成的hashCode和String保存的值有关。因此,如果两个Stnring的值相等,那么他们的hashCode也相等。
对于一个类来说,如果我们在散列集合(HashMap,HashSet,HashTable)中使用他,那么我们没必要重写他的hashCode方法。但是,如果我们需要在散列集合中使用他,我们就应该正确地书写这个hashCode方法。我们知道,在散列集合中,元素存储的位置是和他的hash值有关的,并且散列集合中相同的元素只能有一个。因此,我们设计的hashCode算法应该满足这样的条件:
1.如果A.equals(B),则A的hash值和B的相等。
2.如果A的hash值和B的相等,A不一定equalsB(HashCode冲突)。