- 浏览: 888094 次
- 性别:
- 来自: 武汉
文章分类
最新评论
-
小宇宙_WZY:
膜拜一下大神,解决了我一个大问题,非常感谢 orz
【解惑】深入jar包:从jar包中读取资源文件 -
JKL852qaz:
感谢,遇到相同的问题!
【解惑】深入jar包:从jar包中读取资源文件 -
lgh1992314:
为什么java中调用final方法是用invokevirtua ...
【解惑】Java动态绑定机制的内幕 -
鲁曼1991:
说的都有道理,protected只能被同一级包的类所调用
【解惑】真正理解了protected的作用范围 -
鲁曼1991:
...
【总结】String in Java
Java中判断相等关系一般有两种手段:(1) “==”关系操作符 (2) equals()方法。 显然,基本数据类型变量之间只能用"=="。而对象之间两种手段都是合法的。但是有很多初学者会在“判断Java的相等关系”上面犯错误,这里我们在JVM运行层面上彻底剖析其中的奥秘。如果你对JVM规范不太了解的话,在看本文前请先了解一下JVM运行程序时,在内存中管理的五个运行时数据区,特别是堆和Java栈方面的知识(参见《Java 虚拟机体系结构 》) 。
★ “==”运算符的比较本质
先来看看两段源代码:
//代码1:整型包装器的"=="比较 Integer n1=new Integer(1); Integer n2=new Integer(1); if(n1==n2); //false
//代码2:整型变量的"=="比较 int n3=1; int n4=1; if(n3==n4); //true
代码1的结果让我们感到意外。但在解释这个现象之前,我们首先阐明一个重要的知识点:
JVM运行Java程序,会在内存中会开辟一块叫做“堆 ” 的运行时数据区。在运行过程中所有创建的类对象都存放在这块区域中(准确来说是类的非静态非常量实例数据都存放在堆中)。更重要的是,这些对象的堆空间都有自己的地址,这些地址就是我们常说的 对象引用 。不管是在方法区中还是在Java栈中,存储的都是对象引用,并非对象中的数据。
下面我们看看上面两段代码在JVM中所对应的执行指令:
0 new java.lang.Integer [16] //在堆中分配一个Integer对象n1的空间,并将对象引用(堆地址)压入操作数栈 3 dup //复制对象n1的引用压入操作数栈 4 iconst_1 //将一个整型长度的常量1压入操作数栈 5 invokespecial java.lang.Integer(int) [18] //弹出整型常量1和对象n1的引用,对堆中对象n1的实例数据进行初始化 8 astore_1 [n1] //弹出对象n1的引用,并将其保存在局部变量区的第1个位置上。 9 new java.lang.Integer [16] //对象n2同上 12 dup 13 iconst_1 14 invokespecial java.lang.Integer(int) [18] 17 astore_2 [n2] //将对象n2的引用保存在局部变量区的第2个位置上 18 aload_1 [n1] //将局部变量1中的n1对象引用压入操作数栈 19 aload_2 [n2] //将局部变量2中的n2对象引用压入操作数栈 20 if_acmpne 23 //弹出操作数栈的n1,n2的引用,并比较这两个引用值是否相等。 22 return
0 iconst_1 // 将整型常量1压入操作数栈。 1 istore_3 [n3] //弹出刚压入栈的整型常量1,将其存储在局部变量区的第3个位置上 2 iconst_1 // 将整型常量1压入操作数栈。 3 istore 4 [n4] //弹出刚压入栈的整型常量1,将其存储在局部变量区的第4个位置上 5 iload_3 [n3] //将局部变量3中的整型常量1压入操作数栈 6 iload 4 [n4] //将局部变量3中的整型常量2压入操作数栈 8 if_icmpne 34 //弹出刚压入栈的两个整型常量,并比较这两个整型常量是否相等。 11 return
从代码1的字节码指令可以看出,整型包装器对象n1和n2比较的是对象引用(指令:if_acmpne 23),两个对象在堆中是两块不同的空间,自然地址是不相同的。
而代码2的字节码指令可以看出,整型变量n3和n4比较的是整型常量值,都是1,自然是相同的。
★ equals方法的比较本质
还是来看一段源代码:
Integer n1=new Integer(1); Integer n2=new Integer(1); if(n1.equals(n2)); //true
下面是<Integer> equals(Object obj)方法源代码,比较的是整型值。
public boolean equals(Object obj) { if (obj instanceof Integer) { return value == ((Integer)obj).intValue(); } return false; }
是不是equals方法比较的都是对象的数据值呢?这当然和对象所属的类的equals方法是如何实现的有很大关系。我们再看看一段代码:
Value v1=new Value(1); //Value是自定义类,其中并没有定义equals()方法。 Value v2=new Value(1); if(v1.equals(v2)); //false
Java中Object是所有类的祖先,既然Value没有定义equals()方法。那么上面代码调用的自然是Object的equals()方法。我们看看<Object> equals(Object obj)方法源码,用"=="比较对象的引用。
public boolean equals(Object obj) { return (this == obj); }
如果我们想通过equals方法来达到比较对象中数据值的目的,就必须在指定类中自己实现equals方法来覆盖掉Object的equals方法。千万切忌,如果不覆盖,equals方法的默认行为仍然是比较对象引用。
通过上面,我们已经对"=="和"equals"的本质有了清晰地认识,但匪夷所思的事情仍然会发生。
1、String类型的特殊性造成的“相等比较”疑惑
再看看两段源代码:
String s1=new String("aaaa"); String s2=new String("aaaa"); if(s1==s2); //false if(s1.equals(s2)); //true
String s3="aaaa"; String s4="aaaa"; if(s3==s4); // true if(s3.equals(s4)); //true
代码4很好理解,但代码5有点让人困惑。在解释之前我们要先明确几个问题:
(1) String是类,而并非基本数据类型。s1,s2都是对象实例,而并非基本数据变量。
(2) String s3="aaaa"; 是一种比较特殊的对象创建方法。它涉及到JVM管理方法区中常量池 和拘留字符串对象 的相关问题。在《String in Java 》一文中有详细的总结。
下面查看代码5中"=="比较字符串对象在JVM运行时对应的指令:
0 ldc <String "aaaa"> [13] //将常量池中"aaa"字符串常量指向的堆中拘留String对象的地址压入操作数栈 2 astore_1 //弹出栈顶值,并将其存储在局部变量区的第1个位置上 3 ldc <String "aaaa"> [13] //将常量池中"aaa"字符串常量指向的堆中拘留String对象的地址压入操作数栈 5 astore_2 //弹出栈顶值,并将其存储在局部变量区的第2个位置上 6 aload_1 //将局部变量1压入操作数栈 7 aload_2 //将局部变量2压入操作数栈 8 if_acmpne 11 //弹出两个栈顶值进行比较
很显然,"=="仍然比较的是地址。但是由于压入操作数栈的是字符串常量"aaa"所指向的同一个拘留String对象的地址。因此s3和s4保存的是相同的地址,自然"=="的比较结果也是相同的。
2、Integer类型的自动打包 (autoboxing)机制造成的“相等比较”疑惑
继续看两段代码
Integer a=127; Integer b=127; if(a==b); //结果:true
Integer c=128; Integer d=128; if(c==d); //结果:false
代码6和代码7几乎一样的语句竟然有不同的结果,实在是很困惑。在解释这个问题前仍然要阐明几点:
(1) 源代码中的a、b、c、d并非整型变量,而是整型包装器对象。这一点是肯定。
(2) Integer a=127;这种定义形式比较特殊。原因是编译过程中,编译器做了点小动作。它会自动调用Integer.valueOf(int)方法将整型常量127 打包 (autoboxing)成包装器类。我们叫做自动包装机制
。也就是说JVM运行的是Integer a=Integer.valueOf(127);这条语句。
但还是没有解决代码7,8不同的疑惑呀?下面我们来看看Integer.valueOf(int)的源代码:
/** * Returns a <tt>Integer</tt> instance representing the specified * <tt>int</tt> value. * If a new <tt>Integer</tt> 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. * * @param i an <code>int</code> value. * @return a <tt>Integer</tt> instance representing <tt>i</tt>. * @since 1.5 */ public static Integer valueOf(int i) { final int offset = 128; if (i >= -128 && i <= 127) { // must cache return IntegerCache.cache[i + offset]; } return new Integer(i); } private static class IntegerCache { private IntegerCache(){ } static final Integer cache[]= new Integer[-(-128) + 127 + 1]; static { for(int i = 0; i < cache.length; i++) cache[i] = new Integer(i - 128); } }
看看Integer的源代码就知道了,其实Interger把 -128~127之间的每一个值都建立了一个对应的Integer对象,并将这些对象组织成cache数组,类似一个缓存。
这个缓存数组中的Integer对象是可以安全复用的。也就是Integer a=127;和Integer b=127;中的引用a,b都是缓存数组中new Integer(127)对象的地址。所以代码6中的a==b自然是true。
但要注意了,缓存数组只存储-128~127之间的Integer对象。对于其他的值的整形包装器,比如代码7中的Integer c,d=128分别在堆中创建了两个完全不同的Integer对象用来存储128。两个对象的地址都不一样。
这里提一点:如果是Integer a=new Integer(127);这种常规形式创建的Integer是没有cache数组的。只有Integer a=127或Integer a=Integer.valueOf(127)这样的方式才能使用上cache数组。而且包装器的整形值在-128~127之间。
实际上,这个小技巧对于初学者来说确实造成了麻烦。但是它却是Java性能优化上的一个重要的应用。我们都知道在堆中不停的开辟新对象需要很大的代价。当我们需要大量值在-128~127范围内的整型对象的时候,这样一个cache缓存减少了大量对象的创建,效率提升时可想而知的。
发表评论
-
NIO
2010-08-05 10:36 0在JDK1.4以前,I/O输入输出处理,我们把它称为旧 ... -
【总结】Java线程同步机制深刻阐述
2010-05-16 10:21 5932全文转载:http://www.iteye ... -
【JDK优化】java.util.Arrays的排序研究
2010-05-12 21:06 9112作者题记:JDK中有很多算法具有优化的闪光点,值得好好研究。 ... -
【JDK优化】 Integer 自动打包机制的优化
2010-03-12 19:14 4080我们首先来看一段代码: Integer i=100; In ... -
【总结】Java与字符编码问题详谈
2009-12-30 09:11 9348一、字符集和字符编码方式 计算机只懂得0/1两种信号 ... -
【解惑】 正确理解线程等待和释放(wait/notify)
2009-12-29 13:40 19656对于初学者来说,下面这个例子是一个非常常见的错误。 /** ... -
【解惑】JVM如何理解Java泛型类
2009-12-16 11:08 12226//泛型代码 public class Pair<T& ... -
【解惑】正确的理解this 和 super
2009-12-05 09:46 4414转载: 《无聊 ... -
【解惑】真正理解了protected的作用范围
2009-11-21 18:00 5002一提到访问控 ... -
【总结】String in Java
2009-11-21 17:52 10814作者:每次上网冲杯Java时,都能看到关于String无休无止 ... -
【解惑】真正理解了protected的作用范围
2009-11-16 17:11 585一提到访问控制符protected,即使是初学者 ... -
总结Java标准类库中类型相互转化的方法
2009-11-09 21:57 210组一: ☆ String → byte[ ... -
方法没覆盖住带来的烦恼
2009-11-05 09:18 100Object类是所有类的祖宗,它的equals方法比较的 ... -
【解惑】数组向上转型的陷阱
2009-11-03 11:44 1836问题提出: 有两个类Manager和Em ... -
【总结】java命令解析以及编译器,虚拟机如何定位类
2009-11-01 16:25 5746学Java有些日子了,一直都使用IDE来写程序。这 ... -
【解惑】剖析float型的内存存储和精度丢失问题
2009-10-26 15:10 15824问题提出:12.0f-11.9f=0.10 ... -
【解惑】领略内部类的“内部”
2009-10-19 15:38 3535内部类有两种情况: (1) 在类中定义一个类(私有内部类 ... -
【解惑】深入jar包:从jar包中读取资源文件
2009-10-08 21:13 65429我们常常在代码中读取一些资源文件(比如图片,音乐,文 ... -
【解惑】理解java枚举类型
2009-09-26 09:37 3355枚举类型是JDK5.0的新特征。Sun引进了一个全新的关键字e ... -
编写自己的equals方法
2009-09-20 14:18 129在我的《令人头疼的"相等"关 ...
相关推荐
java解惑java解惑java解惑java解惑java解惑java解惑
Java解惑Java解惑Java解惑Java解惑Java解惑Java解惑Java解惑Java解惑Java解惑Java解惑Java解惑Java解惑
解惑SQL
Java解惑.pdf Java解惑.pdf Java解惑.pdf Java解惑.pdf
JAVA解惑.pdf JAVA解惑.pdf JAVA解惑.pdf
《Java解惑》《Java解惑》《Java解惑》《Java解惑》《Java解惑》《Java解惑》
Java解惑中文版 Java解惑 java健壮程序
《经典JAVA面试题解惑系列合集(臧圩人)》
SQL解惑(第2版)
EXCEL函数公式解惑专集EXCEL函数公式解惑专集
扫描完整版 解惑大数据 解惑大数据 解惑大数据 解惑大数据
IT 学生解惑经典指导书 IT 学生解惑经典指导书 IT 学生解惑经典指导书 IT 学生解惑经典指导书 IT 学生解惑经典指导书 IT 学生解惑经典指导书 IT 学生解惑经典指导书 IT 学生解惑经典指导书
C语言解惑中文版.pdf 扫描版
IT学生解惑真经
IT 学生解惑真经
也许有人看过java解惑,即通过讲述一个案例,解释一个java特性抑或一个容易遇到的小陷阱。SQL解惑与其相同,通过小小案例解析、阐述SQL各个特性,比刻板而唠叨的教科书更小巧、便捷,焕然一新。
"java解惑" PDF版本
JAVA面试题解惑系列.pdf和臧圩人--JAVA面试题解惑系列合集.pdf
JAVA解惑,你面包括一些java经典的问题。