简言:为什么说叫这个标题,因为本人甚是很惭愧,第一是已经很久没有更新博客了,总是各种理由和借口,有一天我看到hyman的一篇博客,那篇博客没有聊技术更多的是聊hyman大神个人写博客的经历,看后甚是感触很大;第二是虽然不更新了博客,但是偶尔还会有一些支持我的朋友路过赞下以前的文章,心里老实说有种莫名的感伤,时间都去哪了。第三为什么叫这个标题,因为发现自己的之前的java学的简直太次了,所以决定在写android的同时,重头开始学习java。这次Java学习之路不同于以前的蜻蜓点水,浪费时间,目的在于加深和巩固。好了,矫情了那么久进入正题,今天来聊下"equals"和“==”的关系。
“equals”与“==”之间的关系可以说大家都已经非常清楚了,但是对于一个重头学习java的我来说,请大神们不要喷。目的在于学习和巩固。这两者关系是android和java面试和笔试题目中经常考核的问题,也很简单。下面将从这几方面聊下他们之间的故事。
一、“equals”和“==”的关系。
大家都很清楚 “equals”比较是两个对象的内容是否相等,而“==”则表示比较的是两个对象的引用是否相等(也即是两个引用是否指向同一个对象)
注意:equals这个方法不适用基本数据类型,而==则适用基本数据类型(故下面讨论的话题,不包含基本数据类型的讨论,只针对引用数据类型)
好就这些了吗?貌似就这些了,没错我第一次学习的时候也就学到这了。一般的笔试题也就考这么简单。但是我觉得下面几个例子也许你会感兴趣的。
第一个非常简单的例子相信你看一眼就知道答案了。
package com.mikyou.test;
public class TestDemo1 {
public static void main(String[] args) {
Integer num1 = new Integer(100);//使用包装器类型(属于引用数据类型),可以使用equals
Integer num2 = new Integer(100);
System.out.println(num1 == num2);
System.out.println(num1.equals(num2));
}
}
输出结果:
输出结果毋庸置疑,证明了我们之前说的equals比较的是两个对象的内容,而"=="则比较的是两个对象的引用是否相等。
好,记住这句话,那么请看第二个例子。
package com.mikyou.test;
public class TestDemo2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Num num1 = new Num();//创建一个自定义Num类的实例对象,属于引用类可以使用equals
num1.n = 100;
Num num2 = new Num();
num2.n = 100;
System.out.println(num1.equals(num2));
}
}
输出结果:
大家可能看到这个结果多少会有些疑惑吧,没错呀,引用类型使用equals比较的是内容,我们分别给num1、num2对象中的n成员赋了相同的值,比较的是内容的话应该输出结果是true才对呀。为何会有这样的思维呢?这就是我们平时不好的习惯,平时就像是在喊口号似的,“equals比较的是内容,==比较的是引用”。貌似我们得出以上分析结论是背下来,而没有去真正的分析为什么会是这样的呢。因为这就是我的真实写照,第一次学Java就是背口号似的把这个给硬记下来了,也不知道为啥。
既然是重头学习,那就一起聊下为什么吧?
注意:实际上equals的默认行为才是真正的比较引用而不是比较内容。可是为何第一个例子对的呢?难道第一个例子输出结果有问题?
其实第一个例子没有问题,很正确,为什么会有这样的结果?大家仔细看下第一个例子用的是Integer类包装器类属于Java中的类库,而第二例子属于我们自己定义的类
问题就出现在这。因为equals的默认行为是比较引用,所以第二个例子当然结果当然就是false了,而第一个例子使用的Integer包装器类型为什么是true,因为在大部分的java的类库中都对equals这个方法进行了覆盖,为了比较方便重写了equals方法,使得所有java类库中的equals方法都是比较内容,从而修改了equals比较引用的默认行为。因为在Java类库用的比较多,所以一般就这样把equals说成比较内容相同。如果不信的话,我们来看下Integer包装器类的源码,源码中equals是否被重写了。
可以看到Integer包装器类中确实重写了equals将一个Object类型转化成Integer类型并取出值比较内容是否相等。
为了做比较我们一起来看下最原始的equals方法长得啥样。
很明显我们可以看到最原始的equals方法比较的是引用而不是内容,比较传入对象引用是否与当前的对象引用相等,而且很明显一个是equals应该是直接属于Object这个类的一个原始方法。所以到这里我想应该对于equals是比较内容还是引用有个更清楚认识吧。
说完上面的“equals”再来聊下“==”中一个有趣的问题。这个问题也是我在逛csdn的时候看到某大神写了的,因为看着与我这篇博客符合,所以放在一起,至于哪个大神不好意思忘了,写进来也就给我这个重头学Java的人一个学习。
不管太多,先来看段很简单很简单的代码:
package com.mikyou.test;
public class TestDemo3 {
public static void main(String[] args) {
Integer num1 = 100;
Integer num2 = 100;
Integer num3 = 200;
Integer num4 = 200;
System.out.println(num1 == num2);
System.out.println(num3 == num4);
}
}
输出的结果:
看到上面的例子你是不是又感到疑惑了,不瞒你说,我刚开始的时候看到也懵逼了,因为第一次Java学的很浅显。没有深入学习。
这个例子按照我们前面例子来说,Integer属于包装器类型,而且==比较的是引用,那么按道理应该输出的是false,false才对呀。如果我们这样分析的话,还是思维定式了,思维太死了。得出这个结论只因为我们对Integer这个包装器类不熟悉。
注意:实际上在Integer包装器类中还有一个IntegerCache内部类,这是Integer包装类中的一个缓存类,用于缓存-128到127大小的整数。首先说下为什么会有这个缓存的类的存在,因为我们在编程过程-128到127范围这些数使用的频繁度更高,为了提高效率和优化性能,所以设计一个缓存的类来帮助实现。
为了更好理解我们来看下Integer类中的IntegerCache源码
/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* sun.misc.VM class.
*/
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() {}
}
从上面的源码我们缓存的范围是-128~127
例子中的Integer num1 = 100;实际上就是Integer num1 = Integer.valueOf(100);
我们可以一起再来看下,Integer中的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);
}
我们可以很明显的看到,首先会判断传入进去的数据是否在缓存中(-128-127)中,如果在的话就直接取缓存中cache数组中的数,这个cache数组中我们清楚看到它是事先就在静态代码块中预先初始化好了,也就是说cache数组中的包含-128~127范围的对象引用。例子中第一次100,因为在缓存中,就去找到cache数组中的100所对应的Integer对象引用,第二次100也还是将这个cache为100的引用赋值。所以两次的100引用所指的是同一个对象,所以输出为true.而超过缓存范围的我们可以看到会重新new 一个Interger对象,所以第一次200不在范围内,所以会new一个新的对象,第二次也会new一个新的对象,很明显不是同一个对象所以输出false。
说到这是不是觉得很神奇。那么既然很神奇的话,不妨做个更加神奇的事。
通过源码我们可以看到缓存的实现主要是通过IntegerCache类中的cache数组,而这个数组的大小就是保存从-128到127的总共为256长度的元素对象。我们可以通过Java中的反射机制,来修改这个缓存数组,从而使得通过Integer创建的对象的数且在该缓存范围的数大小由我们而定。来看这段代码。
package com.mikyou.test;
import java.lang.reflect.Field;
public class TestDemo4 {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
Class cache = Integer.class.getDeclaredClasses()[0]; //得到Integer类中的内部类的Class类型
Field myCache = cache.getDeclaredField("cache"); //我们从源码可看到cache这个数组是static所以直接通过反射出的类即可得到这个缓存数组
myCache.setAccessible(true);//设置个数组是可访问的
Integer[] newCache = (Integer[]) myCache.get(cache);
newCache[127] = newCache[129];
//我们知道cache是-128~127的数组对应0~256数组下标,下标为127的为-1,下标为129的为1;此操作也就是将缓存的中的1覆盖-1
} catch (SecurityException | NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
Integer a = -1;
Integer b = a * 3;
System.out.println(b);//-1 * 3 = -3,可最后b=3,因为通过反射将缓存中的数据-1修改为1,然后1 * 3 =3
}
}
输出结果:
说到这,今天的有关equals和==内容就说到这了,欢迎大家留言,一起分享有关equals和==的故事。好了已经很晚了,该睡了,想想明天的还得早起上班,不过很开心,已经很久都没有这种熬夜写博客的感觉了。