学习了Hash容器之后,让我们来深入分析一下Java中的equals与==操作。
==操作对于基本数据类型变量比较的是两个变量的值,对于引用型变量比较的是两个变量所指向的堆中内容的地址,即栈中的内容。
对于equals,先来看看Object中的equals方法定义:
/*
equals 比较非空对象引用
自反性,x.equals(x)返回true
对称性:x.equals(y)与y.equals(x)结果相同
传递性:如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)返回true
一致性:多次执行x.equals(y)的结果相同
对于任何非空引用x,x.equals(null)返回false;
*/
public boolean equals(Object obj) {
return (this == obj);
}
从这个定义中可以看出:equals的默认操作表示两个变量是否是对同一个对象的引用,或者说是比较所指之对象的内存地址是否相同。
我们经常会这样使用:
String s1 =new String("hello");
String s2 =new String("hello");
System.out.println(s1.equals(s2));//结果为true
虽然s1与s2是两个不同对象的引用(即它们指向内存中不同的空间),但返回的结果却是true,
这是因为String重写了equals方法,其源码是这样的:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])//逐个比较两个string中的字符
return false;
}
return true;
}
}
return false;
}
if (this == anObject) {
return true;
}
这段代码判断转型的情况,比如下面这样的情况:
String s = "hello";
Object o = (Object) s;
System.out.println(s.equals(o));//true
anObject instanceof String
这句判断传入的引用是否指向一个String对象。
如果是,则通过比较每个字符来判断,遇到不相等的字符,则返回false
个人认为,可以先判断两个字符串的长度,如果长度不等,则返回false,因为如果两个很长的字符串只有最后一个字符不相同,
那么需要比较到最后一个字符才能知道结果,这样,不好!不好!
分析这个方法的目的在于说明我们可以通过重写equals方法来自定义equals的实现,默认情况是比较对象的地址,
我们可以像String那样重写为比较对象的内容,或者别的。
我们甚至可以都返回true,这样就和谐了,呵呵!
对于什么时候需要重写equals()方法,《Effective Java》chapter3中有详细描述.
简单数据类型和封装类中的equals和==
Java 为每一个简单数据类型提供了一个封装类,每个基本数据类型可以封装成对象类型。除 int ( Integer )和 char ( Character ),其余类型首字母大写即成封装类类型名。
double (Double), float(Float),long(Long), short(Short),byte(Byte),boolean(Boolean).
以int和Integer为例说明
Java中int和Integer区别如下:
1.int是基本的数据类型,默认值可以为0;
2.Integer是int的封装类,默认值为null;
3.int和Integer都可以表示某一个数值;
4.int和Integer不能够互用,因为他们是两种不同的数据类型;
来分析下面这些表达式:
int a1 = 1;
int a2 = 1;
Integer b1 = new Integer(1);
Integer b2 = new Integer(1);
System.out.println(a1 == a2);//true
System.out.println(b1 == b2);//false
System.out.println(b1.equals(b2));//true
System.out.println(b1.equals(a1));//true
System.out.println(a1==b1);//true
a1==a2 这个是成立的,必须的!
b1==b2 表达式的值为false,虽然是相同的数据类型,但是它们是两个对象,在堆上占用着不同的空间,==比较的是2个对象的地址,它们的地址是不相等的;
b1.equals(b2)==true 这个是成立的,表达式的值为true. 相同数据类型,两个对象,地址不同,内容相同, quals比较的是2个对象的内容,所以成立。
Integer中重写了equals:
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
public int intValue() {
return value;
}
b1.equals(a1),结果为true。因为equals比较的是两个对象,所以a,b都不能为基本数据类型,否则会出编译错误。(在jdk1.5以上版本中,由于会对基本数据类型进行自动封装,b1可以为基本数据类型,a1不可以)
a1==b1;结果为true 因为此时Integer对象b1会自动解封。
同理,其它的封装类和基本类型也是这样的情况。
分析一下关于String的特殊情况:
String s1 = "Hello"; // String literal
String s2 = "Hello"; // String literal
String s3 = s1; // same reference
String s4 = new String("Hello"); // String object
String s5 = new String("Hello"); // String object
System.out.println("s1==s2:"+(s1==s2));
System.out.println("s1==s4:"+(s1==s4));
System.out.println("s3==s4:"+(s3==s4));
System.out.println("s4==s5:"+(s4==s5));
结果为:
s1==s2:true
s1==s4:false
s3==s4:false
s4==s5:false
s4==s5:false
这个结果好理解,s3与s4指向内存中的两个不同的对象(虽然对象的内容是相同的)
s1==s3:false
s1==s2:true
s3==s4:false
这三个结果与String的特殊之处有关。
请看下图:
上图已经一目了然了,s1、s2、s3、s4、s5这五个引用所指对象的内容虽然都为"Hello",但在内存中的位置是不一样的。
s1、s2、s3指向常量池中的"Hello"字符串,而s4、s5指向堆中的两个对象。
java中的常量池技术是为了方便快捷地创建某些对象而出现的,当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),
则在需要重复重复创建相等变量时节省了很多时间。
常量池其实也就是一个内存空间,不同于使用new关键字创建的对象所在的堆空间。
java中基本类型的包装类的大部分都实现了常量池技术,这些类是Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。
下一篇将深入分析equals()与hashcode()的关系。