大家都知道,==是比较引用的地址,而equals是比较实例的具体成员,我们可以通过重载equals来实现自己的判断方法,但是 ==
和equals其实是挺复杂的,请看下面这个示例:
@Test
public void test1() throws Exception {
int a = 1;
Integer b = a;
// 1
System.out.println("a == b is " + (a == b));
System.out.println("b.equals(a) is " + b.equals(a));
b = new Integer(1);
// 2
System.out.println("a == b is " + (a == b));
System.out.println("b.equals(a) is " + b.equals(a));
}
大家可能猜测 1应该都是true,而2应该是都是false true对吧。因为第一次b只是把引用指向了a,第二次则是重新实例化了对象。
然而结果却是:
a == b is true
b.equals(a) is true
a == b is true
b.equals(a) is true
这是为什么呢?接着看下个例子你就明白了,
@Test
public void test1() throws Exception {
int a = 1;
Integer b = a;
// 1
System.out.println("a == b is " + ((Integer)a == b));
System.out.println("b.equals(a) is " + b.equals(a));
b = new Integer(1);
// 2
System.out.println("a == b is " + ((Integer)a == b));
System.out.println("b.equals(a) is " + b.equals(a));
}
输出是:
a == b is true
b.equals(a) is true
a == b is false
b.equals(a) is true
==是比较引用的地址,而equals是比较实例的具体成员
这句话确实没错,但是他却说的是比较对象的情况,
而比较基本类型时:
1 == 2
true == false
这样却是比较值,从深入理解来说可能还是比较地址,因为有所谓的常量池的存在。但我们可以简单的认为就是比较值了。
== 在比较基本类型和包装类型时会先把包装类型转成基本类型,然后再比较其值。
在第二片段代码中: ((Integer)a == b)
就变成了比较对象,此时比较的便是地址。所以第一次 == 比较结果是true,第二次 ==
比较时结果是false。
还是Integer,我们接着看下面这次比较:
@Test
public void test2() throws Exception {
Integer a = new Integer(1);
Integer b = Integer.valueOf(1);
System.out.println("a == b is " + (a == b));
System.out.println("b.equals(a) is " + b.equals(a));
}
如果答案是 false、true你会吃惊吗?
首先第一点,a是实例化的对象,它不指向常量池。
其次 b 是 valueOf,valueOf则有一个缓存机制:
/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
所以b和a指向的并不是同一个实例。
接下来这个例子也有点挑战:
@Test
public void test3() throws Exception {
Integer a = Integer.valueOf(128);
Integer b = Integer.valueOf(128);
System.out.println("a == b is " + (a == b));
System.out.println("b.equals(a) is " + b.equals(a));
}
valueOf都从缓存取,那么指向的地址应该都是一样的吧,然而,结果却是:
a == b is false
b.equals(a) is true
hhh,这是因为Integer缓存的范围是-127 -- 127
把128改成127就没错了:
@Test
public void test3() throws Exception {
Integer a = Integer.valueOf(127);
Integer b = Integer.valueOf(127);
System.out.println("a == b is " + (a == b));
System.out.println("b.equals(a) is " + b.equals(a));
}
输出:
a == b is true
b.equals(a) is true
String 也是比较复杂的,但是如果你真的理解了,那么你对于java底层的理解也会更深一层,
先从简单的例子看起:
@Test
public void test4() throws Exception {
String a = "123";
String b = "12" + 3;
String c = "12" + '3';
String d = "12" + "3";
System.out.println("a.equals(b) is " + a.equals(b));
System.out.println("a == b is " + (a == b));
System.out.println("a == c is " + (a == c));
System.out.println("a == d is " + (a == d));
}
挺好理解的,都是true,因为都是常量,计算完 之后去常量池找,常量池不存在则放入,存在则返回其引用。
然后就是一个复杂的例子:
@Test
public void test5() throws Exception {
String a = "123";
String b = new String("123");
String c = b.intern();
System.out.println("a.equals(b) is " + a.equals(b));
System.out.println("a == b is " + (a == b));
System.out.println("a == c is " + (a == c));
System.out.println("b == c is " + (b == c));
}
结果如下,你猜对了吗 》》
a.equals(b) is true
a == b is false
a == c is true
b == c is false
- a == b 为false很好理解,因为一个是常量引用,一个是实例化对象的引用,所以他们指向的值不同
- a == c 为true,是因为intern这个函数的机制决定的,它会返回常量池中对应字符串的引用,如果常量池中不存在该字符串,则会先将该字符串放入常量池,然后再返回其引用。
- b == c 为false 也和 2一个道理,就不细说了
以下是intern的源码注释:
/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* <p>
* It follows that for any two strings {@code s} and {@code t},
* {@code s.intern() == t.intern()} is {@code true}
* if and only if {@code s.equals(t)} is {@code true}.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* <cite>The Java™ Language Specification</cite>.
*
* @return a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
*/
public native String intern();
中文即是:
/ **
* 返回字符串对象的规范表示形式。
* <p>
* 最初为空的字符串池由
* 类{@code String}私有维护。
* <p> 调用intern方法时,如果池已经包含一个等于该字符串的字符串,
* 则该字符串由{@link #equals(Object)}方法确定,则该字符串来自池返回。
* 否则,将此{@code String}对象添加到 池中,并返回对此{@code String}对象的引用。 <p>
* 因此,对于任意两个字符串{@code s}和{@code t},
* {@code s.intern()== t.intern()}为{@code true}
* 仅当{@code s.equals(t)}为{@code true}时。
* <p>
* 所有文字字符串和字符串值常量表达式都是内联的。字符串文字是在<cite> Java™语言规范</ cite>的第3.10.5节中定义的。
*
* @return 与该字符串具有相同内容的字符串,但是保证来自唯一字符串池。
* /
== 和 equals应该还有更好玩的地方,留着以后慢慢挖掘了..
版权声明
本文章由作者“衡于墨”创作,转载请注明出处,未经允许禁止用于商业用途
评论区#
还没有评论哦,期待您的评论!
引用发言