两个对象比较都有哪些坑

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

大纲

两个对象比较都有哪些坑

对象的比较

对象的比较,有多种方式,常用的“==”和 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

(0)
码上实战的头像码上实战

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!