equal 和 “==” 两者均表示相等的意思,但是他们相等的含义却是有区别。

“==”在基本数据类型的时候,通过比较它们的实际值来判定是否相等;而用于比较引用类型的时候则是比较引用值是否相等,也就是是否指向同一对象。

对于基本数据类型与引用类型用“==”比较的时候,JDK1.5以后,会对引用类型通过拆箱机制将其转化成基本类型,再比较。

在java.lang.Object中有equals方法,其中默认的实现方法是和“==”相同的,其他类可以对equals方法进行重写。


实例代码1:

public class Main {
	public static void main(String args[]){
		String a = "123";
		String b = "123";
		String c = new String("123");
		String d = new String("123");
		int e = 128;
		Integer f = new Integer(128);
		
		System.out.println(a==b);  
		System.out.println(a==c);  
		System.out.println(a.equals(c));
		System.out.println(c.equals(d)); 
		System.out.println(e==f); 
		System.out.println(f.equals(e)); 
	}
}



运行结果为: true

                         false

                         true

                         true

                         true

                         true

1. a==b : 当两个对象使用“==”比较的时候,比较的是引用值是否相等,String a = "123"会创建一个对象,所以a与b是指向同一对象的,所以a==b为true。

2. a==c:  a与c不是指向同一对象的,所以a与c引用值不想等,所以返回false。

3. a.equals(c) : String类的equals方法的实现是比较字符串的字符序列化值是否相等,所以返回true。

4. c.equals(d): equals比较的是两个对象是否相等,由于两个对象的值都是“123”,所以返回true。

5. e==f: e为基本数据类型,f为引用类型,在通过“==”相比较的时候会将引用类型通过拆箱机制转化为基本类型,再进行比较,所以返回true。

6. f.equals(e): Integer类中的equals方法的实现是一个与该对象包含相同 int 值的 Integer 对象时,结果为true


基本类型和基本类型的包装类。基本类型有:byte、short、char、int、long、boolean。基本类型的包装类分别是:Byte、Short、Character、Integer、Long、Boolean。注意区分大小写。二者的区别是:基本类型体现在程序中是普通变量,基本类型的包装类是类,体现在程序中是引用变量。因此二者在内存中的存储位置不同:基本类型存储在栈中,而基本类型包装类存储在堆中。上边提到的这些包装类都实现了常量池技术,另外两种浮点数类型的包装类则没有实现。另外,String类型也实现了常量池技术。

实例代码2:

public class test {  
        public static void main(String[] args) {      
            objPoolTest();  
        }  
      
        public static void objPoolTest() {  
            int i = 40;  
            int i0 = 40;  
            Integer i1 = 40;  
            Integer i2 = 40;  
            Integer i3 = 0;  
            Integer i4 = new Integer(40);  
            Integer i5 = new Integer(40);  
            Integer i6 = new Integer(0);  
            Double d1=1.0;  
            Double d2=1.0;  
              
            System.out.println("i=i0\t" + (i == i0));  
            System.out.println("i1=i2\t" + (i1 == i2));  
            System.out.println("i1=i2+i3\t" + (i1 == i2 + i3));  
            System.out.println("i4=i5\t" + (i4 == i5));  
            System.out.println("i4=i5+i6\t" + (i4 == i5 + i6));      
            System.out.println("d1=d2\t" + (d1==d2));   
              
            System.out.println();          
        }  
    }



结果:

i=i0    true  
i1=i2   true  
i1=i2+i3        true  
i4=i5   false  
i4=i5+i6        true  
d1=d2   false

结果分析:

1.i和i0均是普通类型(int)的变量,所以数据直接存储在栈中,而栈有一个很重要的特性:栈中的数据可以共享。当我们定义了int i = 40;,再定义int i0 = 40;这时候会自动检查栈中是否有40这个数据,如果有,i0会直接指向i的40,不会再添加一个新的40。

2.i1和i2均是引用类型,在栈中存储指针,因为Integer是包装类。由于Integer包装类实现了常量池技术,因此i1、i2的40均是从常量池中获取的,均指向同一个地址,因此i1=12。

3.很明显这是一个加法运算,Java的数学运算都是在栈中进行的Java会自动对i1、i2进行拆箱操作转化成整型,因此i1在数值上等于i2+i3。

4.i4和i5均是引用类型,在栈中存储指针,因为Integer是包装类。但是由于他们各自都是new出来的,因此不再从常量池寻找数据,而是从堆中各自new一个对象,然后各自保存指向对象的指针,所以i4和i5不相等,因为他们所存指针不同,所指向对象不同。

5.这也是一个加法运算,和3同理。

6.d1和d2均是引用类型,在栈中存储指针,因为Double是包装类。但Double包装类没有实现常量池技术,因此Doubled1=1.0;相当于Double d1=new Double(1.0);,是从堆new一个对象,d2同理。因此d1和d2存放的指针不同,指向的对象不同,所以不相等。


小结:


1.以上提到的几种基本类型包装类均实现了常量池技术,但他们维护的常量仅仅是【-128至127】这个范围内的常量,如果常量值超过这个范围,就会从堆中创建对象,不再从常量池中取。比如,把上边例子改成Integer i1 = 400; Integer i2 = 400;,很明显超过了127,无法从常量池获取常量,就要从堆中new新的Integer对象,这时i1和i2就不相等了。

2.String类型也实现了常量池技术,但是稍微有点不同。String型是先检测常量池中有没有对应字符串,如果有,则取出来;如果没有,则把当前的添加进去。


凡是涉及内存原理,一般都是博大精深的领域,切勿听信一家之言,多读些文章。我在这只是浅析,里边还有很多猫腻,就留给读者探索思考了。希望本文能对大家有所帮助!


脚注:


(1) 符号引用,顾名思义,就是一个符号,符号引用被使用的时候,才会解析这个符号。如果熟悉linux或unix系统的,可以把这个符号引用看作一个文件的软链接,当使用这个软连接的时候,才会真正解析它,展开它找到实际的文件

对于符号引用,在类加载层面上讨论比较多,源码级别只是一个形式上的讨论。

当一个类被加载时,该类所用到的别的类的符号引用都会保存在常量池,实际代码执行的时候,首次遇到某个别的类时,JVM会对常量池的该类的符号引用展开,转为直接引用,这样下次再遇到同样的类型时,JVM就不再解析,而直接使用这个已经被解析过的直接引用。

除了上述的类加载过程的符号引用说法,对于源码级别来说,就是依照引用的解析过程来区别代码中某些数据属于符号引用还是直接引用,如,System.out.println("test" +"abc");//这里发生的效果相当于直接引用,而假设某个Strings = "abc"; System.out.println("test" + s);//这里的发生的效果相当于符号引用,即把s展开解析,也就相当于s是"abc"的一个符号链接,也就是说在编译的时候,class文件并没有直接展看s,而把这个s看作一个符号,在实际的代码执行时,才会展开这个。

参考文章:


java内存分配研究:http://www.blogjava.net/Jack2007/archive/2008/05/21/202018.html

深入Java核心 Java内存分配原理精讲:http://developer.51cto.com/art/201009/225071.htm