面试题系列早就想着要写,但是一直没有下笔,感觉不好写,因为写的浅了没有意思,也没有多大的参考价值,如果写的深了,一篇博客的篇幅只够写一道面试题,这个大篇幅的写就失去了写面试题系列的意义,还不如一篇一篇的写。纠结了很久还是决定开始写,在写的过程中细节的部分能省就省,尽量言简意赅,留给看的人更大的思考空间。在此立碑,如果细节不到的,请多多包涵。
==和equals的区别
这个问题博主曾经在刚入行的时候被问过,简直被问的一脸懵逼,现在回头来看,其实并没有那么难,只要知道==是比较地址值,equals比较是equals方法实现逻辑所决定就很好理解了。下面开始看一下。
首先看两个字符串的比较:
String str1 = "123";
String str2 = "123";
System.out.println(str1 ==str2);//输出结果1
String str3 = new String("123");
String str4 = new String("123");
System.out.println(str1 ==str2);//输出结果2
上面的输出结果分别是true和false。
结果1:因为在直接创建字符串的时候,字符串的值会作为常量存储在常量池中,此时str1的引用指向"123",在创建str2的时候会到常量池中查找这个值"123",刚好上一步已经创建,不需要重复创建,str2的引用也指向了这个"123",也就意味着str1和str2指向的是常量池中同一块地址,同一个值。所以比较的结果就很简单了。
结果2:这个和上面的不同之处在于后者在堆中创建了对象,而前者没有,每次执行new的时候都会在堆中创建一个String对象,且这两个对象所存在的地址不同,因此在==比较的时候结果是false。
上面的==比较理解了,再说说equals,这个就不用举例了,但是需要去看一下Object.class源码。
public boolean equals(Object obj) {
return (this == obj);
}
看到Object的equals源码可以知道,其实本质也是使用的==
实现。但是为什么上面说equals准确的说是看实现呢,这个就是因为所有的类都是继承于Object的,与生俱来就有equals方法,如果子类不重写equals方法,那么默认使用的就是==
。这样就可以看的出来,这时使用==比较和equals比较并没有本质的区别。
但是实际上很多需要比较的类都实现并重写了equals方法,这个时候equals比较是否相等就具有很大的不确定性了,而是根据需求来做定制化的。有兴趣的可以看一下String的equals源码,它是先比较==
,如果是true就是直接返回,如果是false再去比较字面值。
总结:在面试的时候常问到的就是字面值比较,然后会问对象的比较,如果在刁钻一点可能会问到基本数据类型的自动拆装包、浮点型等对比较的影响(下面一个面试题来说),所以在使用这两个比较的时候,能用equals就用equals,尽量避免使用==
比较。
基本数据类型、包装类型等比较问题
其实这个问题可以放在上面的说,但是感觉放一起会很冗长,会让人看的晕头转向的,分开写更为的简洁。看代码:
int a = 111;
int a1 = 128;
Integer b = 111;
Integer c = 111;
Integer d = new Integer(111);
Integer e = new Integer(111);
Integer f = 128;
Integer g = 128;
Float h = 123.1F;
Float i = 123.1F;
System.out.println(a == b);//结果1:true
System.out.println(b == c);//结果2:true
System.out.println(c == d);//结果3:false
System.out.println(d == e);//结果4:false
System.out.println(f == g);//结果5:false
System.out.println(h == i);//结果6:false
System.out.println(a1 == f);//结果7:true
这个运行结果如果对基本类型和装拆箱不是很理解的话,很容易就会判断错误。
结果1:a是基本类型,b的类型是包装类型,他们比较的结果是true,原因在于在将111直接赋值给b、c的时候,常量池中已经存在了一个111,直接将引用地址赋值b、c,没有了装箱的过程,也就没有对象创建的过程,因此这个结果为true。
结果2:这个同样可以用结果1中的解释说明。
结果3:new创建了一个对象,这个对象会存在堆中,d引用的是堆里面对象的地址而不是111在常量池中的地址,这时两个比较目标的地址不同,自然就是false。
结果4:两个都是通过new创建的,堆中会有两个对象,d、e分别引用两个不同的对象地址,结果是false。
结果5:这个为什么是false呢,这个比较和结果2的比较基本相同,就是值变了一下。这里就是考察基本功了,在java里面会将-128~127维护在常量池中,也就是说这个范围的值是与生俱来的,在使用的时候直接引用就可以了,但是超过这个范围的时候,就有一个装箱的过程,也就相当于new Integer(128)
,所以就同结果4了,结果必然是false。
结果6:这里只要知道浮点型在赋值的时候必然会装箱,这个结果就不难理解啦。
结果7:这个过程为什么是true呢,原因在于包装类型和基本类型比较的时候,有一个拆箱的过程,也就是将包装类型拆箱后再与基本类型比较,结果自然就是true啦。
总结:这几个结果分析的有点绕,因为不同情况下拆箱装箱过程都不相同,很容易混淆。还有一种是在笔试题中经常出现。如下:
byte i = 1;
i = i +1;//这里编译时就会报错
short s = 1;
s += 1;//这个不会报错
这个有兴趣的可以了解一下了。
还有就是已经有了基本类型为什么还要有包装类型这样的问题。这里只做引出,具体看大家。