以下是本人学习JAVA时的一点理解和感悟,如果有不对的地方还请大家批评指正。
JAVA和C语言一个很大的不同就是:
在C语言中我们可以通过“&”很容易的获取一个对象的地址,而在JAVA中,我们却似乎找不到什么方法可以获取到对象的地址(有人可能以为hashcode就代表地址,其实不然,两个不同的对象hashcode完全可能一样),但绝不代表JAVA中没有地址的概念,只是出于安全性考虑被JVM屏蔽了而已。
OK,下面简介一下JAVA中的堆、栈、常量池,以及在这些地址中都存放一些什么东西。
1. 简单说来,new出来的对象都是放到堆中,注意是对象的内容,而对应的引用(我们可以理解为C语言中的地址)存放在栈中。
比如 String str = new String("abc");
其中str的内容“abc”就存放在堆中,而我们怎么访问到堆中的数据呢,这就需要一个引用,即abc存放在堆中的地址,而这个地址就存放在栈中,通过栈中的地址就能够访问到堆中的内容,而栈的地址我们是无法获知的。
所以我们访问str的过程是先从栈中找到str的地址,然后在堆中查找这个地址对应的内容。
2. 第1条可能大家都会理解,而我们在编程序的时候貌似不会这么写,一般都简写为:
String str = "abc";
貌似差不多的写法实际是有区别的,JVM会按如下步骤去做:
1) 首先在栈中生成一个str的空引用。
2) 查看常量池中是否存在“abc”,如果有就不新建。如果不存在,则在常池中新创建一个。最后更新引用,指向常量池中的"abc"。
常量池是在编译时就能够确定的值,包括字符串,整数,然后直接写到class文件中。
我们做一个验证
Integer c1 = 10;
Integer d1= 10;
System.out.println(c1 == d1);
Integer e = new Integer(10);
Integer f = new Integer(10);
System.out.println(e == f);
这里的==其实就是比较对象是否是同一个对象,是绝对的相等,即地址相同。
有了上面的基础,我们很容易猜到应该输出:
true(指向常量池中同一个内容)
false(动态在堆中生成了2个对象)
那么我们可能很容易的回答下面的程序:
Integer c2 = 128;
Integer d2 = 128;
System.out.println(c2 == d2);
true
可答案并非我们想象,输出的是false,这是因为JVM只会将大于等于-128,小于等于127的整数存放到常量池中。怎么证明我的说法呢,看JAVA源代码:
public static Integer valueOf(int i) {
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
如果大于127,则JAVA会自动将Integer i = 128;转换为Integer i = new Integer(128);
总结一下,对于基本类型的包装类只有Byte,Short,Integer,Long,Character,Boolean会存放到常量池中,并且需要大于等于-128,小于等于127,而对于浮点数和其他整数则存放到堆中。
3. 对于基本数据类型,int, short, long, byte, float, double, boolean, char,为了追求速度,其引用和数值都直接存放到栈中。与常量池相同,如果定义两个相同值的变量,栈中只会保存一份数据。如:
int a = 1;
int b = 1;
其实a和b会指向同一块栈地址,不会创建值的拷贝,而b的值如果变成2,不会改变值,而会寻找是否存在2这个内容,如果存在,则指向新的地址,源地址的内容不会发生改变。
4. Object中定义了equal和hashcode方法,如下:
public boolean equals(Object obj) {
return (this == obj);
}
public native int hashCode();
可见其实equal是和==一样的,都是比较对象是否相同,即地址是否相同。可惜很多继承的类都复写了equal,就拿Integer来举例:
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
发现Integer将equal修改为比较内容,这其实是符合我们日常使用的,我们比较Integer的时候不会比较其地址是否一致,只会关心值是否相同。
而hashcode是一个本地方法,我们无法查看。但本人理解,hashcode与地址完全是两个概念,不能混为一谈。hashcode其实是经过hash算法生成的1个key,好的hash算法能保证不同的对象尽量生成不同的hash值,key重叠的概率越小,代表hash算法越好,如果key不小心一致了,则会以类似链表的形式追加到这个key对应的对象的后面。而相同的对象一定要生成相同的key。那么hashcode到底有什么用呢?
我们都用过Set,它和list的区别是Set不能存放相同的数据,那么当我们添加元素的时候需要比较是否与已经添加的元素相同,如果不同才可以添加,否则不可以添加。
如果没有hashcode我们会怎么做,加一个元素,我们会与已经加入的所有元素进行比较,如果相同则放弃加入,如果元素过度,那么时间复杂度将会是线性增加。
而如果有了hashcode会发生什么变化,我们会首先根据对象生成一个hashcode,如果此key中尚不存在值,表明Set中不存在此对象,如果key中存在对象呢,此时就不得不用equal方法比较了,如果发现没有与其equal的,则会以链表的形式添加到对象后面,此时两个对象的hashcode一致。否则,认为已经存在此对象,不做添加。此时效率大大提升,基本只需要一次比较,因为hash算法保证不同的对象基本不会出现hashcode一致的现象,当然如果比较的对象比较多,hashcode重叠的比较多,效率就会下降。
首先给出一段程序表明hashcode不代表地址:
int sameNum = 0;
List<Integer> list = new ArrayList<Integer>();
for(int i = 0; i< 3000; i++){
Object obj = new Object();
if(list.contains(obj.hashCode())){
sameNum++;
System.out.println("Find it! The index " + i + " has existed. HashCode is " + obj.hashCode());
}else{
list.add(obj.hashCode());
};
}
System.out.println("hashcode same number is " + sameNum);
System.out.println("hashcode different number is " + list.size());
输出结果:
Find it! The index 1705 has existed. HashCode is 14850080
hashcode same number is 1
hashcode different number is 2999
可见在1705时hashcode就一样了,当循环的次数增大到50000,重复的概率会骤然增加,下面是50000时的输出结果:
Find it! The index 2059 has existed. HashCode is 9578500
Find it! The index 2358 has existed. HashCode is 14850080
Find it! The index 11292 has existed. HashCode is 21836611
Find it! The index 13007 has existed. HashCode is 31489342
Find it! The index 15249 has existed. HashCode is 27940859
Find it! The index 20781 has existed. HashCode is 14274282
Find it! The index 21150 has existed. HashCode is 28656525
Find it! The index 21196 has existed. HashCode is 22474382
Find it! The index 22533 has existed. HashCode is 9396085
Find it! The index 25148 has existed. HashCode is 9883920
Find it! The index 25419 has existed. HashCode is 16939205
Find it! The index 26404 has existed. HashCode is 1948811
Find it! The index 26685 has existed. HashCode is 24944885
Find it! The index 26777 has existed. HashCode is 12518719
Find it! The index 29223 has existed. HashCode is 14093690
Find it! The index 30355 has existed. HashCode is 1408579
Find it! The index 32790 has existed. HashCode is 15329177
Find it! The index 33371 has existed. HashCode is 18582092
Find it! The index 33472 has existed. HashCode is 17905186
Find it! The index 34065 has existed. HashCode is 15846907
Find it! The index 34405 has existed. HashCode is 4639333
Find it! The index 34997 has existed. HashCode is 3658896
Find it! The index 35473 has existed. HashCode is 23470672
Find it! The index 36403 has existed. HashCode is 26538728
Find it! The index 36718 has existed. HashCode is 23304757
Find it! The index 37309 has existed. HashCode is 16602152
Find it! The index 38496 has existed. HashCode is 3397361
Find it! The index 38600 has existed. HashCode is 28556557
Find it! The index 40634 has existed. HashCode is 5377162
Find it! The index 41385 has existed. HashCode is 24480816
Find it! The index 42521 has existed. HashCode is 3938856
Find it! The index 42843 has existed. HashCode is 18435317
Find it! The index 43240 has existed. HashCode is 18996396
Find it! The index 43272 has existed. HashCode is 14709257
Find it! The index 44081 has existed. HashCode is 32522360
Find it! The index 44218 has existed. HashCode is 32532484
Find it! The index 44822 has existed. HashCode is 7003951
Find it! The index 45153 has existed. HashCode is 30448342
Find it! The index 45420 has existed. HashCode is 27105466
Find it! The index 45660 has existed. HashCode is 31521182
Find it! The index 48871 has existed. HashCode is 12680961
Find it! The index 48924 has existed. HashCode is 20948677
Find it! The index 49012 has existed. HashCode is 11729238
Find it! The index 49385 has existed. HashCode is 33288263
Find it! The index 49926 has existed. HashCode is 4922317
hashcode same number is 45
hashcode different number is 49955
可见hashcode不能代表地址。当然我们可以根据需要复写hashcode和equal,让我们自己决定添加进Set中的元素是否重复。