Equals和==的区别

来源:互联网 发布:批量域名查询 编辑:程序博客网 时间:2024/06/06 00:09

在阐述equals和==的区别前,我们要先简单说一下JVM中内存分配的问题。

当我们创建一个对象时,会在堆内存中开辟一段空间来存储对象数据,同时在栈内存中生成该对象的引用。后续代码调用时使用的都是栈内存中的引用。

特别地,基本数据类型是存储在栈内存中的

基本数据类型

对于基本数据类型,直接用双等号(==)去比较它们的值。

复合数据类型

JAVA所有的类都是继承自Object这个基类的,可以从Object类中的equals方法实现中看出,equals方法比较的是对象的引用是否相同。这点上,和==是一致的。
除非,如String、Integer、Date等,一些类中的equals方法被重写,它们不再是比较类在堆内存中的存放地址了。

    public boolean equals(Object obj) {        return (this == obj);    }

String

重点看一下String对象的equals方法应用。

我们可以通过很多途径创建String对象,既可以通过new操作,双引号(“”)和”+”操作符进行实例化,也可以从char array, byte array, StringBuffer and StringBuilder中获得String对象。而JVM使用字符串常量池保存了所有的String对象。

我们来看下面一段代码。

    String s = new String("abc");      String s1 = "abc";      String s2 = new String("abc");      System.out.println(s == s1);      System.out.println(s == s2);      System.out.println(s1 == s2);  

(1)String s = new String("abc");
这条语句的实际执行过程是,括号里的”abc”先到字符串常量池中看看有没”abc”这个对象,没有则在pool里创建这个对象,所以这里就在pool中创建了一个”abc”对象。然后通过new操作实例化了一个”abc”对象,存放在堆区。这里的s不是对象,只是引用,指向堆区里的对象。

(2)String s1 = "abc";
这条语句的实际执行过程是,”abc”到字符串常量池中看看有没”abc”这个对象,很显然,上一条语句已经在pool里创建了一个”abc”。所以这条语句没有创建对象,s1指向的是pool中的”abc”。

(3)String s2 = new String("abc");
这条语句和第一条语句类似,不同的是括号里的”abc”在pool中会找到之前创建的”abc”,它不会在pool中再创建对象了。相同的是会在堆区开辟一段内存,实例化一个新的”abc”对象。s2指向的是这个新的”abc”对象。当然,s和s2指向的是不同的对象。

来分析下运行结果,
s指向堆里的对象,s1指向字符串常量池里的对象,明显不同。
s与s2指向堆里不同的对象,虽然内容都是”abc”,但它们的物理地址并不相同。
s1与s2的比较同第一种比较,都返回false。

关于intern()方法的使用。

    String s = new String("abc");    String s1 = "abc";    String s2 = new String("abc");    System.out.println(s == s1.intern());    System.out.println(s == s2.intern());    System.out.println(s1 == s2.intern());

s1.intern()的执行流程是在字符串常量池里去查找s1对应的内容(也就是”abc”)。如果找到,则返回pool里的对象,如果就在pool里创建这个对象并返回。

这样就很容易理解了,来看一下运行结果,
s1.intern()返回的是pool里的”abc”对象,与s指向的堆里的对象肯定不同,返回false。
s2.intern()返回的是pool里的”abc”对象,与s指向的堆里的对象肯定不同,返回false。
s1与s2.intern()都指向了pool中的”abc”对象,返回true。

关于字符串拼接的场景。

    String hello = "hello";    String hel = "hel";    String lo = "lo";    System.out.println(hello == "hel" + "lo");    System.out.println(hello == "hel" + lo);

首先明确的是”hello”、”hel”和”lo”这三个都指向pool中的对象。

“hel”+”lo”按照内容来说,相加也就是”hello”。这时会返回pool中的”hello”对象。所以,hello == “hel” + “lo”返回的是true。

而”hel”+lo虽然内容也是”hello”,但是它将在堆里面生成一个”hello”对象,并返回这个对象,所以这里的结果是false。

关于对象拼接的场景。
intern()方法的功能是“如果常量池中存在当前字符串,就会直接返回当前字符串。如果常量池中没有此字符串,会将此字符串放入常量池中后,再返回”。设计的初衷就是重用String对象,以节省内存消耗。

JDK1.6以及以前版本中,常量池是放在Perm区(属于方法区)中的,熟悉JVM的话,应该知道这是和堆区完全分开的。

JDK1.6以后常量池被放置在了堆空间,因此常量池位置的不同会影响String的intern()方法的表现。
这里写图片描述

    String s3 = new String("1") + new String("1");    s3.intern();    String s4 = "11";    System.out.println(s3 == s4);

(1)String s3 = new String("1") + new String("1");
这行代码在字符串常量池中生成“1” ,并在堆空间中生成引用s3指向的对象(内容为”11”)。注意,此时常量池中是没有”11”对象的。

(2)s3.intern();
这一行代码将s3中的“11”字符串放入字符串常量池中。

JDK1.6的做法是直接在常量池中生成一个”11”的对象;在JDK1.7中,常量池中不再存储一份对象了,而是直接存储堆中的引用。这份引用直接指向 s3引用的对象,也就是说s3.intern()==s3会返回true。

(3)String s4 = "11";
这一行代码会去常量池中创建”11”,但发现已经有这个对象了,也就是指向s3引用对象的一个引用。因此,s3 == s4返回了true,也就是说它们都指向了堆区存储的对象。

最后,再看一个对象拼接的例子。

    String str1 = new String("a") + new String("bc");    System.out.println(str1.intern() == str1);    System.out.println(str1 == "abc");

str1.intern() == str1就是上面例子中的情况,str1.intern()发现常量池中不存在”abc”,便指向了str1。”abc”在常量池中创建时,也就直接指向了str1了。两个判断都会返回true。

    String str2 = "abc";//新加的一行代码,其余不变      String str1 = new String("a") + new String("bc");    System.out.println(str1.intern() == str1);    System.out.println(str1 == "abc");

str2在常量池中创建了”abc”,那么str1.intern()就直接指向了str2,后面的”abc”也一样指向str2。那么和堆空间中的str1都无关了,两个判断都会返回false。

参考:http://blog.csdn.net/seu_calvin/article/details/52291082