今天在空闲时间聊天时发现,面试题中的equals问题,以及String、Integer等的判等问题仍是讨论的比较激烈并且混乱。。。(滑稽)java
其实网上有很是多关于这种面试题的文章或者博客,其实多去看看就能够。面试
不过。。。看了好多,都是通篇理论,感受很干。思考以后,决定开一个新的模块,经过源码来解释面试题中、或者常见的一些问题中的原理以及本质,帮助有疑惑的小伙伴更容易、更深刻的理解原理,以及相应的源码设计。数组
说到正题,这篇文章讨论的是关于equals在不一样对象、以及特殊类型String、Integer上的实际原理。缓存
这一部分属于J2SE最基础的东西了,算是常识性的,也没什么好说的。app
基本数据类型,只有==,它是比较两个变量的值是否一致ui
引用数据类型的比对须要区别对待this
就由于这些最基本的知识,引起了不少面试题,我们一一列举编码
public class Demo { public static void main(String[] args) throws Exception { String str1 = "123"; String str2 = "123"; String str3 = new String("123"); String str4 = new String(str1); StringBuilder sb = new StringBuilder("123"); System.out.println(str1 == str2); System.out.println(str1 == str3); System.out.println(str1 == str4); System.out.println(str3 == str4); System.out.println(str1.equals(str2)); System.out.println(str1.equals(str3)); System.out.println(str1.equals(sb.toString())); System.out.println(sb.equals(str1)); } }
上面的源码列举了最多见的几种判断String是否相等的状况,小伙伴们能够先不要往下拉,先思考一下输出结果都应该是怎样的。。。设计
。。。。。。code
。。。。。。
。。。。。。
。。。。。。
。。。。。。
。。。。。。
输出结果:1 5 6 7为true,其他为false(你都答对了吗)
下面我们来一一分析。
String str1 = "123"; String str2 = "123"; System.out.println(str1 == str2); // true System.out.println(str1.equals(str2)); // true
第一种状况,要了解Java虚拟机在处理String时的特殊机制:字符串常量池。
简单来讲,若是某一个字符串在程序的任意位置被声明出来,则Java虚拟机会将该字符串放入字符串常量池缓存起来,若是后续还有其余字符串变量须要引用该字符串,只须要将该变量引用指向常量池内的字符串常量便可。
固然,这个机制的前提是:String类必须是不可变的。
【源码】String类的声明:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
String被声明为final,也就是不容许有子类,同时一个String就是一个不可变的对象。
因此从上面的解释中,也就明白了,两个对象其实都是指向了字符串常量池中的同一个字符串常量!因此用==比对的结果是true。
那==都是true了,对于equals方法来说,那确定也是true。
String str1 = "123"; String str3 = new String("123"); String str4 = new String(str1); System.out.println(str1 == str3); // false System.out.println(str1 == str4); // false System.out.println(str3 == str4); // false System.out.println(str1.equals(str3)); // true
这几种状况是面试问的最多的。。。
网上大片的理论核心都是说:由于构造方法总会建立新的对象,因此上面的str1, str3, str4都各不相同。。。
可为何是这样呢?
【源码】String的构造方法
/** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0 public String(String original) { this.value = original.value; this.hash = original.hash; }
能够看到,传入String的构造方法,实际上是将参数中String的value赋给当前正在实例化的String对象,而这个value是一个char[]。
也就是说,这两个对象,是两个彻底不一样的对象,但共享同一个char[]。
因此==比对的是内存地址,天然也就不一样。但为何equals方法返回的是true呢?
【源码】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; }
String类重写了equals方法,因此再也不是仅仅调用==而已。。。
上面也提了,若是两个对象的内存地址都相同,那天然是同一个对象,因此在equals方法中也处理了这一步。
关键是下面的if结构:
在这里,针对String进行额外的比对,也就是比对char[]中的内容是否彻底一致。
而上面的几种状况,都是由"123"这个字符串复制而来,因此char[]天然都是同一个,也就证实这三个String对象,内容相同,但内存地址不一样。
String str1 = "123"; StringBuilder sb = new StringBuilder("123"); System.out.println(str1.equals(sb.toString())); // true System.out.println(sb.equals(str1)); // false
StringBuilder能够理解是一种字符串拼接中间件吧,它的内部其实也是维护了一个char[]。
【源码】StringBuilder与其父类的部分源码
public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence{ public StringBuilder(String str) { super(str.length() + 16); append(str); } //...... } abstract class AbstractStringBuilder implements Appendable, CharSequence { /** * The value is used for character storage. */ char[] value; /** * The count is the number of characters used. */ int count; //...... }
说点题外话,原题中这种状况下,这个StringBuilder对象sb,在实例化时将一个String传入,实际上是将这个String塞入内部的char[],并额外扩容16个空间。
也就是:[123 ](16个空格)
回到正题上。那在比较一个String对象和一个StringBuilder对象时,就须要看StringBuilder有没有重写equals方法。
查看源码,使用IDE的方法搜索,并无找到StringBuilder有重写equals方法,父类亦如此。
也就是说,最终调用的仍是Object的equals方法,而。。。
【源码】Object的equals方法
public boolean equals(Object obj) { return (this == obj); }
Object的equals方法,也就是==
显而易见,一个是String,一个是StringBuilder,类型都不相同,确定就不一样了。。。
那为何str1.equals(sb.toString())会返回true呢?
【源码】StringBuilder的toString方法
public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }
在这里,它从新建立了一个String对象,这个对象是截取了StringBuilder内部维护的char[]的一部分,这个count是最后一个有字符的数组索引+1,那天然就是有内容的那一部分。
那原来的str1,内容就是"123",上面构造的StringBuilder,内容也是"123",那调用toString方法时,返回的天然也就是"123",最后两个字符串进行比对,天然返回true。
public class Demo { public static void main(String[] args) throws Exception { Integer num1 = 100; Integer num2 = 100; Integer num3 = 200; Integer num4 = 200; Integer num5 = new Integer(100); Integer num6 = new Integer("100"); Integer num7 = Integer.valueOf(100); Integer num8 = Integer.valueOf("100"); Integer num9 = Integer.parseInt("100"); Long l1 = 100L; Integer num10 = l1.intValue(); System.out.println(num1 == num2); System.out.println(num3 == num4); System.out.println(num1 == num5); System.out.println(num1 == num6); System.out.println(num1 == num7); System.out.println(num1 == num8); System.out.println(num1 == num9); System.out.println(num1 == num10); System.out.println(num5 == num6); System.out.println(num5 == num7); } }
上面的源码列举了最多见的几种判断Integer对象是否相等的状况,小伙伴们能够先不要往下拉,先思考一下输出结果都应该是怎样的。。。
。。。。。。
。。。。。。
。。。。。。
。。。。。。
。。。。。。
。。。。。。
输出结果:1 5 6 7 8为true,其他为false(你都答对了吗)
下面我们来一一分析。
Integer num1 = 100; Integer num2 = 100; Integer num3 = 200; Integer num4 = 200; System.out.println(num1 == num2); // true System.out.println(num3 == num4); // false
咱们都知道,jdk1.5后引入了自动装箱机制,基本数据类型能够在编码时直接隐式转换为引用数据类型(包装类)。
实际上,咱们编写的这段源码,通过编译再反编译,拿到的反编译源码 ↓
【反编译】上述源码的反编译后的结果:
Integer num1 = Integer.valueOf(100); Integer num2 = Integer.valueOf(100); Integer num3 = Integer.valueOf(200); Integer num4 = Integer.valueOf(200);
那既然在进行==对比时还打印了true,惟一的可能性是Integer.valueOf方法,两次返回了同一个对象!
那咱们就须要追到valueOf方法中一探究竟。
【源码】Integer的部分源码:
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} }
能够看到,在valueOf方法中,其实是先判断传入的数是否在一个范围内:若是在,会从IntegerCache中取,不然直接建立新的Integer对象。
这个IntegerCache是一个高速缓冲区,它缓冲了从-128到127之间的全部整数的包装对象(源码中low=-128, high=127)
传入的数为100,在缓冲区范围内,故两次都取的缓冲区的对象,天然也就是同一个对象了。
传入的数为200,不在缓冲区范围内,故两次都调用了构造方法,天然就是两个不一样的对象。
Integer num1 = 100; Integer num5 = new Integer(100); Integer num6 = new Integer("100"); System.out.println(num1 == num5); // false System.out.println(num1 == num6); // false System.out.println(num5 == num6); // false
构造方法,一定是建立新对象,因此上述实际是一个高速缓冲区的对象和两个独立建立的对象。
【源码】Integer的两个构造方法:
public Integer(int value) { this.value = value; } public Integer(String s) throws NumberFormatException { this.value = parseInt(s, 10); }
可见两种构造方法都是将值赋给了即将实例化的对象的value成员中,那天然就是不一样对象了。
Integer num1 = 100; Integer num8 = Integer.valueOf("100"); Integer num9 = Integer.parseInt("100"); System.out.println(num1 == num8); // true System.out.println(num1 == num9); // true
valueOf不说了,上面提到了,是从高速缓冲区取对象,可是parseInt方法呢?
注意:不要被这种陷阱迷惑!parseInt的返回值是int!
public static int parseInt(String s) throws NumberFormatException { return parseInt(s,10); }
也就是说,返回值为int,还不能直接将值赋给num9,还须要多作一步,也就是valueOf。。。
说白了仍是从高速缓冲区拿。。。
【反编译】上述源码反编译的结果:
Integer num1 = Integer.valueOf(100); Integer num8 = Integer.valueOf("100"); Integer num9 = Integer.valueOf(Integer.parseInt("100"));
分明就是执行了三次valueOf!
Integer num1 = 100; Long l1 = 100L; Integer num10 = l1.intValue(); System.out.println(num1 == num10); // true
调用Long对象的intValue操做,其实看方法名也能看出来,返回的是int。。。又是int。。。
好了,不用我说了,都明白这种骚套路了吧,又是valueOf。。。
也甭看源码了,直接看反编译的结果吧。。。
【反编译】上述源码反编译的结果:
Integer num1 = Integer.valueOf(100); Long l1 = Long.valueOf(100L); Integer num10 = Integer.valueOf(l1.intValue());
好吧,原来到最后都是valueOf方法,这波骚套路我算是记下来了。。。
(完)