- 浏览: 898039 次
- 性别:
- 来自: 武汉
文章分类
最新评论
-
小宇宙_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 6005全文转载:http://www.iteye ... -
【JDK优化】java.util.Arrays的排序研究
2010-05-12 21:06 9193作者题记:JDK中有很多算法具有优化的闪光点,值得好好研究。 ... -
【JDK优化】 Integer 自动打包机制的优化
2010-03-12 19:14 4203我们首先来看一段代码: Integer i=100; In ... -
【总结】Java与字符编码问题详谈
2009-12-30 09:11 9428一、字符集和字符编码方式 计算机只懂得0/1两种信号 ... -
【解惑】 正确理解线程等待和释放(wait/notify)
2009-12-29 13:40 19771对于初学者来说,下面这个例子是一个非常常见的错误。 /** ... -
【解惑】JVM如何理解Java泛型类
2009-12-16 11:08 12379//泛型代码 public class Pair<T& ... -
【解惑】正确的理解this 和 super
2009-12-05 09:46 4474转载: 《无聊 ... -
【解惑】真正理解了protected的作用范围
2009-11-21 18:00 5095一提到访问控 ... -
【总结】String in Java
2009-11-21 17:52 10987作者:每次上网冲杯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 1890问题提出: 有两个类Manager和Em ... -
【总结】java命令解析以及编译器,虚拟机如何定位类
2009-11-01 16:25 5824学Java有些日子了,一直都使用IDE来写程序。这 ... -
【解惑】剖析float型的内存存储和精度丢失问题
2009-10-26 15:10 16087问题提出:12.0f-11.9f=0.10 ... -
【解惑】领略内部类的“内部”
2009-10-19 15:38 3600内部类有两种情况: (1) 在类中定义一个类(私有内部类 ... -
【解惑】深入jar包:从jar包中读取资源文件
2009-10-08 21:13 65950我们常常在代码中读取一些资源文件(比如图片,音乐,文 ... -
【解惑】理解java枚举类型
2009-09-26 09:37 3427枚举类型是JDK5.0的新特征。Sun引进了一个全新的关键字e ... -
编写自己的equals方法
2009-09-20 14:18 129在我的《令人头疼的"相等"关 ...
相关推荐
java 解惑 java 解惑 java 解惑 java 解惑 java 解惑 java 解惑
#### 标题:臧圩人--JAVA面试题解惑系列合集 此标题表明文档集合了一系列由臧圩人为Java初学者准备的面试题解析,旨在帮助读者深入理解Java的核心概念和技术,增强面对实际编程挑战的能力。 #### 描述:“java初学...
在IT领域,SQL(Structured Query Language)是一种用于管理和处理关系数据库的强大工具。SQL解惑解惑,意味着我们将深入探讨SQL的常见问题、概念以及解决策略。在这个过程中,我们不仅要理解SQL的基本语法,还要...
《经典JAVA面试题解惑系列合集(臧圩人)》这本书很可能就是针对这些问题进行深入解析的一本指南。 首先,Java的基础知识是面试中的必考部分。这包括但不限于Java语法、数据类型、控制结构(如if语句、for循环、...
从关系型数据库如MySQL到非关系型数据库如MongoDB,了解数据库的设计原理、查询语言SQL以及如何优化数据库性能,对于开发高效的应用程序至关重要。 操作系统是计算机的灵魂,书中的内容可能涵盖操作系统原理,如...
《Java解惑(中文版)》是一本专为Java初学者设计的学习资料,旨在帮助读者解答在学习Java过程中遇到的各种困惑。"solve65p"可能代表这本书包含65个问题或主题,每个都深入浅出地进行了讲解,旨在解决初学者在编程...
例如,一个人(Person类)要过河,需要借用一条船(Boat类),这时人与船之间就存在一种依赖关系。 ### 泛化(Generalization) 泛化,也称为继承或类属,是UML中最常见的一种关系,表示一个类(子类)继承另一个...
"java解惑" PDF版本
在《SQL解惑 第二版》中,作者深入浅出地讲解了SQL的基础概念,如SELECT语句、JOIN操作、子查询、聚合函数、排序与分组等,并通过具体实例让读者能更好地理解和应用这些知识。 1. SELECT语句:这是SQL中最基础的...
《IT学生解惑真经》可能涵盖关系型数据库的基本概念,如SQL语言的使用,数据库设计原则(如范式),以及数据库性能优化策略。随着大数据时代的到来,NoSQL数据库和数据仓库也可能被提及。 软件工程实践也是书中的...
IT 学生解惑经典指导书 IT 学生解惑经典指导书 IT 学生解惑经典指导书 IT 学生解惑经典指导书 IT 学生解惑经典指导书 IT 学生解惑经典指导书 IT 学生解惑经典指导书 IT 学生解惑经典指导书
本书《找事:给年青一代的就业解惑书》是著名经济学家何帆教授的新作,该书旨在指导年轻人找到自己的职业道路,而不仅仅是找到一份工作。书中探讨了当前就业市场的变化、职业发展的新机遇和挑战,提出了“找事”概念...
《Java解惑中文版》是一本专为Java程序员设计的指南,旨在帮助读者解决在编程过程中遇到的各种问题,提升程序的健壮性。本书深入浅出地探讨了Java语言的核心概念、常见疑惑以及最佳实践,旨在使开发者能够编写出更...
《解惑SDN》这本书由樊勇兵、陈楠、黄志兰、陈天四位作者共同撰写,由人民邮电出版社出版。这本书是一本高清扫描版的著作,共计19个章节,且配备了完整的目录,使得读者能够方便地查阅和理解SDN(Software Defined ...
文档《java解惑 PDF版》中列举了95个这样的谜题,每个谜题都旨在帮助开发者理解并纠正一些常见的错误理解。以下是根据提供的部分内容解析的几个相关知识点。 ### 表达式谜题与取余操作符(%)的行为 在Java中,...
- **类与对象**:Java是面向对象的语言,理解类的定义、对象的创建以及它们之间的关系是基础。 - **封装、继承和多态**:这三个面向对象的特性是理解Java程序设计的关键。 - **访问修饰符**:public、private、...
本书通过丰富的实例和深入浅出的讲解,让读者能够快速掌握C语言的核心概念和技术要点。 #### 二、核心知识点详解 ##### 1. 基础语法与数据类型 - **变量声明与初始化**:在C语言中,变量必须先声明后使用。例如,`...
4. 指针:掌握指针的基本概念和操作,包括指针与数组的关系,以及指针与函数的关系。 5. 结构体和联合体:了解如何定义和使用结构体和联合体来处理复杂的数据类型。 6. 预处理器指令:学会使用宏定义、条件编译...
《IT解惑》是一部综合性的资源集合,包含了《IT学生解惑真经》、《程序员羊皮卷》和《高质量C编程指南》三部分,旨在为计算机科学与技术的学习者和未来的职业程序员提供全面的指导和建议。这些文档分别关注了IT学生...