大家好,我们开发的时候,经常会遇到两个对象比较大小,那么对象比较都有哪些?又有哪些坑呢,今天我们一起来聊聊两个对象比较的注意事项。
大纲

对象的比较
对象的比较,有多种方式,常用的“==”和 Object.equals(), 还有Objects.equals,ObjectUtils.equals等等。
我们知道“==”,用于比较两个对象的地址是否相同,对于基本数据类型“==”用于比较两个数的值。equals用于比较两个对象的值,但需要对象所属类重写父类的equals方法。
我们来看看JVM的内存分配

以Object obj = new Object()为例,obj为对象的引用,存放于JVM中的栈中。
new Object()为对象的实例,存放于堆中。

字符串和基本类型等常量的值,存放在常量池中。
等号比较
Integer比较
先看看以下代码,分析下比较的结果
Integer a = Integer.valueOf(1);
Integer b = Integer.valueOf(1);
Integer c = Integer.valueOf(256);
Integer d = Integer.valueOf(256);
System.out.println(a==b);
System.out.println(c==d);
四个Integer对象,按基本类型int比较,a==b预期值为true;c==d预期的值为true。
那么我们看看实际运行的结果
System.out.println(a==b);//true
System.out.println(c==d);//false
看到运行后的结果,是不是大跌眼镜啊,明明c和d的值相等,为何c==d的值为false。而a和b的值也相等,而a==b的值又为true?
Integer为常用的数值类型,在Jdk定义中,-128到127之间的值,经常用到,被定义为常量,常量在JVM中,被存储于JVM的常量池中。
a和b值为1,在-128到127之间,为常量,存放于常量池中,a和b指向的相同常量池中的常量,因而a==b为true,也就是,a和b指向的是相同的常量地址。
c和d的值为128,在-128和127之外,非常量,存放于堆heap中,在堆中分别分配了存储空间,c和d指向的内存空间地址不同,因而c==d为false。
在开发中,遇到Integer类型定义时,应注意其特殊性,避免写出与预期不符的代码。
这个坑隐藏的比较深,开发时应十分留意。
String比较
String a = "Hello Java!";
String b = "Hello Java!";
String c = new String("Hello Java!");
String d = new String("Hello Java!");
System.out.println(a==b);//true
System.out.println(a==c);//false
System.out.println(d==c);//false
abcd为对象的引用,“Hello Java!”为字符串常量,存放于常量池中,故a和b的引用,指向的是常量池中的同一个常量,故a==b结果为true。
c指向new出来的对象实例,对象实例存储与JVM的堆中,a和c指向不同的对象,因而a==c为false。
d指向new处理的另一个对象实例,同样存储于堆中,c和d指向不同的对象,因而c==d为false。
空值比较
Integer a = null;
Integer b = Integer.valueOf(1);
System.out.println(a==b);//false
System.out.println(b==a);//false
Integer类型的a指向空对象,Integer类型的b指向常量池中的常量1。
可以看到,无论空对象在前还是在后,使用==比较时,值均为false。
因为两个Integer变量指向了不同的对象。
基本类型比较
int a = 1;
Integer b = Integer.valueOf(1);
int c = 256;
Integer d = Integer.valueOf(256);
System.out.println(a == b);//true
System.out.println(c == d);//true
两个int类型比较,如果对应的值一样,使用==号时为true。
int和Integer使用==比较时,会按自动拆箱的方式,Integer转换为int类型比较,故c==d为true。
再看另一个例子:
long a = 1;
Integer b = Integer.valueOf(1);
byte by = 1;
long c = 256;
Integer d = Integer.valueOf(256);
System.out.println(a == b);//true
System.out.println(a==by);//true
System.out.println(b==by);//true
System.out.println(c == d);//true
a的类型为long,b的类型为Integer,两者比较时,b先自动拆箱,再转换为long类型后,与a比较值的大小,因而a==b结果为true。
by的类型为1,b的类型为byte,两者比较时,b先自动拆箱,再将by转为int类型,与b比较值的大小,因而b==by结果为true。
Object.equals比较
我们看看Object的equals方法源码
public boolean equals(Object obj) {
return (this == obj);
}
Object的equals为两个对象的地址是否相同。
对象默认继承于Object,equals为Object的方法,对象需要比较值时,应重写Object对象的equals方法。
Integer比较
Integer a = Integer.valueOf(1);
Integer b = Integer.valueOf(1);
Integer c = Integer.valueOf(256);
Integer d = Integer.valueOf(256);
System.out.println(a.equals(b));//true
System.out.println(c.equals(d));//true
abcd四个变量都是Integer类型,a和b的数值相同,c和d的值相同。
a和b比较的结果为true,c和d比较的结果也为true。
还记得Integer类型用==比较么,a和b用等号比较的结果为true,c和d用等号比较的结果为false。
这里c和d,用equals比较的结果,为啥又是true呢?
那么我们来看看Integer的equals方法
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
Integer的equals方法,先判断对象的类型是否为Integer,若是,则自动拆箱后比较值的大小。如果类型不同,直接返回false。
Long的equals方法与Integer的类似,不再复述,感兴趣的朋友可以自行查看源码。
String比较
String a = "Hello Java!";
String b = "Hello Java!";
String c = new String("Hello Java!");
String d = new String("Hello Java!");
System.out.println(a.equals(b));//true
System.out.println(a.equals(c));//true
System.out.println(c.equals(d));//true
例子中,a和b指向了字符串常量“Hello Java!”, c和d指向new出来的对象,对应的内存地址不同。
使用equals比较时,无论a和b,还是a和c,或者c和d比较的结果都为true。
c和d为两个不同的对象,为何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) {
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;
}
先看a和b的地址是否相同,相同则返回true;当a和b的地址不同时,再看是否为String类型,非String类型返回false,String类型的,则比较两个字符串的长度,长度相同的情况下,每个字符逐一比较,完全一致则返回true;否则返回false。
String重写了equals方法,只要值相同,则equals后的结果就为了true。
空值比较
Integer a = null;
Integer b = Integer.valueOf(1);
System.out.println(b.equals(a));//false
System.out.println(a.equals(b));//java.lang.NullPointerException
a指向类型为Integer的空对象,b指向类型为Integer的常量池中的常量1。
当非空对象调用方法equals比较时,结果为false.
当空对象调用方法equals比较时,因为this对象为空,空对象调用equals会报NullPointException异常。
空指针异常,也是开发中常见的问题,在做代码扫描时,一般这种问题会被第一时间发现,并提示修正。
基本数据类型比较
Integer a = Integer.valueOf(1);
long b = 1;
int c = 2;
Long d = Long.valueOf(2);
System.out.println(a.equals(b));//false
System.out.println(d.equals(c));//false
基本数据类型的equals比较,this应该为基本数据类型的包装类。
我们拿a和b变量来说,a为Integger类型,即基本类型int的包装类。
b为long类型,属于基本类型。
故两者比较比较时,基本类型的变量只能放后面,即变量顺序为a,b。
同样c和d变量中,d变量为基本类型的包装类,c为基本类型,变量的顺序为d, c。
a和b比较时,this对象为Integer类型,我们知道,Integer的equals比较时,会判断另一个对象是否为Integer类型,如果不是则为false。
而另一个对象的类型的包装类为Long,非Integer,故比较的结果为false。
d和c比较时,也是一样,d为Long包装类类型,c的包装类为Integer类型,两个类型不一致,因而结果为false。
所以,问题就来了,明明值相同的整数,但类型不同时,使用equals时,得到的结果为false。
这就提醒我们,在整数比较时,如果类型不同,一定不能使用equals来比较,否则会得到非预期的结果,而这种情况,因与值比较预期不符,也比较难发现原因,开发时,需谨慎留意。
这个坑一定要十分小心。
Objects.equals比较
先看下Objects.equals的源码
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
先看a和b是否指向相同的对象,相同的对象则直接返回true。
非相同对象时,a不为空的情况下,通过a对象的equals方法进行比较;a为空时,返回false。
Integer比较
int a = 1;
Integer b = Integer.valueOf(1);
System.out.println(Objects.equals(a, b));//true
System.out.println(Objects.equals(b, a));//true
a为基本数据类型,b为Integer类型,指向常量池中的常量1。
在比较时,因a==b为true,所以比较的结果为true。
调换a和b的顺序,b==a也为true,比较的结果也是一样的,为true。
String比较
String a = "Hello Java!";
String b = "Hello Java!";
String c = new String("Hello Java!");
String d = new String("Hello Java!");
System.out.println(Objects.equals(a, b));//true
System.out.println(Objects.equals(a, c));//true
System.out.println(Objects.equals(c, d));//true
a和b指向字符串常量“Hello Java!”, c和d分别指向两个不同的字符串对象,四个字符串类型的值比较值,最终调用的是String类的equals方法,那么只要字符串的值相同,则返回true。
空值比较
String a = null;
String b = "Hello Java!";
System.out.println(Objects.equals(a, b));//false
System.out.println(Objects.equals(b, a));//false
a和b的地址不同,a为空,所以结果为false。
当参数为b,a顺序时,因a为null,判断a instanceof String为false,故最终结果为false。
这里不会报空指针异常,当其中一个对象为空时。
基本数据类型比较
int a = 1;
long b = 1;
System.out.println(Objects.equals(a, b));//false
System.out.println(Objects.equals(b, a));//false
System.out.println(Objects.equals(b, (long)a));//true
a为int类型的常量1,b为long类型,值为1。a和b比较时,返回的结果为false。
当将a强制类型转为long类型后,在和a进行比较,结果却为true。
明明值相同,为啥结果又不同,这又是因为啥原因?
因为a和b装箱后的数据类型不同,故返回的结果为false。当把a强制转换为long类型后,与b类型相同,故equals后的结果为true。
与Object的equals方法类似,Integer或Long类型进行比较时,两个对象的类型要相同,否则哪怕值相同,结果也为false。
这个坑,开发时也需留意。
ObjectUtils.equals比较
先看下ObjectUtils.equals的源码
public static boolean equals(Object object1, Object object2) {
if (object1 == object2) {
return true;
}
if ((object1 == null) || (object2 == null)) {
return false;
}
return object1.equals(object2);
}
比较两个变量object1和object2,如果两个变量的对象地址相同,则返回true。
如果两个变量,任何一个为空,则返回false。最后,按变量object1类型的equals来比较,返回比较的结果。
ObjectUtils的equals实现与Objects的equals实现基本一样,两个工具类比较的结果是一样的。
Integer比较
int a = 1;
Integer b = Integer.valueOf(1);
Integer c = new Integer(128);
Integer d = new Integer(128);
System.out.println(ObjectUtils.equals(a, b));//true
System.out.println(ObjectUtils.equals(b, a));//true
System.out.println(ObjectUtils.equals(c, d));//true
基本类型int变量a和其包装类Integer变量b比较,在a==b判断时为true,故结果为true。
int的包装类Integer的变量c和d比较,在c==d判断时为false,c和d两个变量非空,则以Integer的equals方法做比较,即比较包装类的基本类型是否相等,相等则返回true。
String比较
String a = "Hello Java!";
String b = "Hello Java!";
String c = new String("Hello Java!");
String d = new String("Hello Java!");
System.out.println(ObjectUtils.equals(a, b));//true
System.out.println(ObjectUtils.equals(a, c));//true
System.out.println(ObjectUtils.equals(c, d));//true
比较的结果与使用Objects.equals比较的结果相同,即地址相同则返回true。
若有人一个值为空,则返回false。
若两个字符串的值相同,则返回true。
空值比较
String a = null;
String b = "Hello Java!";
System.out.println(ObjectUtils.equals(a, b));//false
System.out.println(ObjectUtils.equals(b, a));//false
字符串类型,a为空值,b为固定值。
变量a和b比较时,两个变量的地址不同,则进行下一步判断。
其中变量a为空,则返回false。
变量b和a比较时,变量b不为空,但变量a为空,则返回false。
基本数据类型比较
int a = 1;
long b = 1;
System.out.println(ObjectUtils.equals(a, b));//false
System.out.println(ObjectUtils.equals(b, a));//false
System.out.println(ObjectUtils.equals(b, (long)a));//true
变量a为基本类型int,变量b为基本类型long,两个变量的数值都为1。
a和b比较时,因a和b的包装类不同,故a==b为false。
a和b都非空,采用包装类的equals方法比较,因a和b的包装类不同,故返回false。
将a强制类型转换为long时,a和b的变量地址不同,故a==b为false。
a和b的包装类均为Long,故比较值相等,故返回true。
总结
对象的比较,首先需要知道对象在JVM中储存的位置,是存在堆空间,还是元空间,亦或是栈中。
存放在堆中的对象,在堆中的地址不同,使用==比较时,会得到false的结果。
Integer对象的比较,对于-128到127的值,会作为常量存放于常量池中,故使用==比较时值等则返回为true,非-128到127之间数据的对象,如果地址相同则为true,地址不同则为false。
基本类型的对象使用equals比较时,this对象的类型应为基本类型的包装类方可,同时,比较时会先转换为对应的包装类,在做对比。
equals比较,无论是Object类的还是Objects类的,亦或是ObjectUtils类的,比较的是两个对象的值,要看对象所属类的equals方法的具体实现。
两个对象在比较时,使用equals方法时,若对象属于不同的类,结果一定是false。
Object的equals比较时,this对象不能为空,为空时会报空指针异常。
故使用equals比较时,应尽量避免直接使用Object类的equals方法,而应使用Objects或ObjectUtils的equals方法。
原文始发于微信公众号(扬哥手记):两个对象比较都有哪些坑
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/290294.html