浅谈 java中的== 和equals

今天,看到这样的一个问题,例如

Integer n1 = new Integer(2);

Integer n2 = new Integer(2);

System.out.println(n1 == n2); 显示的结果为  false;

System.out.println(n1.equals(n2)); 显示的结果为  true;

为什么 n1 == n2 不是等于 true呢?带着这个疑问,我查找了很多资料,总结为以下:

一, 关系操作符“==” 到底比较的是什么

下面这个句话是摘自《Java编程思想》一书中的原话:

“关系操作符生成的是一个boolean结果,它们计算的是操作数的值之间的关系”。

这句话看似简单,理解起来还是需要细细体会的。说的简单点,==就是用来比较值是否相等

     int  n=5;

     int  m=5;

     System.out.println(n==m); 显示结果为  true , 这个很容易理解,变量n和变量m存储的值都为3,肯定是相等的

     Integer n1 = new Integer(2);

     Integer n2 = new Integer(2);

     System.out.println(n1 == n2); 显示结果为 false, 为什么为false,  要理解这个其实只需要理解基本数据类型变量和非基本数据类型变量的区别

     

    1) 基本数据类型

     在Java中游8种基本数据类型:

  浮点型:float(4 byte), double(8 byte)

  整型:byte(1 byte), short(2 byte), int(4 byte) , long(8 byte)

  字符型: char(2 byte)

  布尔型: boolean(JVM规范没有明确规定其所占的空间大小,仅规定其只能够取字面值"true"和"false")

  对于这8种基本数据类型的变量,变量直接存储的是“值”,因此在用关系操作符==来进行比较时,比较的就是 “值” 本身。要注意浮点型和整型都是有符号类型的,而char是无符号类型的(char类型取值范围          为0~2^16-1).

   2) 非基本数据类型

     而对于非基本数据类型的变量,在一些书籍中称作为 引用类型的变量。比如上面的n1就是引用类型的变量,引用类型的变量存储的并不是 “值”本身,而是于其关联的对象在内存中的地址。

     Integer n1 = new Integer(2);

     通常把这条语句的动作称之为创建一个对象,其实,它包含了四个动作。

     1)右边的“new Integer”,是以Integer类为模板,在堆空间里创建一个Integer类对象(也简称为Integer对象)。

     2)末尾的()意味着,在对象创建后,立即调用Integer类的构造函数,对刚生成的对象进行初始化。构造函数是肯定有的。如果你没写,Java会给你补上一个默认的构造函数。

     3)左边的“Integer n1”创建了一个Integer类引用变量。所谓Integer类引用,就是以后可以用来指向Integer对象的对象引用。

     4)“=”操作符使对象引用指向刚创建的那个Integer对象。

     我们可以把这条语句拆成两部分:

     Integer n1;

     n1 = new Integer(2);

     效果是一样的。这样写,就比较清楚了,有两个实体:一是对象引用变量,一是对象本身。

       在堆空间里创建的实体,与在数据段以及栈空间里创建的实体不同。尽管它们也是确确实实存在的实体,但是,我们看不见,也摸不着。不仅如此,

       我们仔细研究一下第二句,找找刚创建的对象叫什么名字?有人说,它叫“Integer”。不对,“Integer”是类(对象的创建模板)的名字。

       一个Integer类可以据此创建出无数个对象,这些对象不可能全叫“Integer”。

       对象连名都没有,没法直接访问它。我们只能通过对象引用来间接访问对象。

       为了形象地说明对象、引用及它们之间的关系,可以做一个或许不很妥当的比喻。对象好比是一只很大的气球,大到我们抓不住它。引用变量是一根绳, 可以用来系汽球。

       如果只执行了第一条语句,还没执行第二条,此时创建的引用变量n1还没指向任何一个对象,它的值是null。引用变量可以指向某个对象,或者为null。

       它是一根绳,一根还没有系上任何一个汽球的绳。执行了第二句后,一只新汽球做出来了,并被系在n1这根绳上。我们抓住这根绳,就等于抓住了那只汽球。

      简单的说就是:

      n1指向了一个对象(很多地方也把n1称作为对象的引用),此时变量n1中存储的是它指向的对象在内存中的存储地址,并不是“值”本身,也就是说并不是直接存储的数值2。

      因此在用==对n1和n2进行比较时,得到的结果是false。因此它们分别指向的是不同的对象,也就是说它们实际存储的内存地址不同。

      若执行:

      Integer n3 = n1;

      System.out.println("n1 == n3"); 显示结果为true  因为n3指向了n1指向的对象,即n1和n3指向的是同一个对象

二.equals比较的又是什么?

  equals方法是基类Object中的方法,因此对于所有的继承于Object的类都会有该方法。为了更直观地理解equals方法的作用,直接看Object类中equals方法的实现。

  该类的源码路径为:C:\Program Files\Java\jdk1.6.0_14的src.zip 的java.lang路径下的Object.java(视个人jdk安装路径而定)。

  下面是Object类中equals方法的实现:

  

  很显然,在Object类中,equals方法是用来比较两个对象的引用是否相等,即是否指向同一个对象。

      注意到没有:默认Object类的equals方法是比较两个对象的地址,跟==的结果一样,

      那为什么 

      Integer n1 = new Integer(2);

      Integer n2 = new Integer(2);

      System.out.println(n1.equals(n2));的结果为true呢?那是因为类库Integer类中的equals方法有其自身的实现,而不再是比较在堆内存中的存放地址,它们比较的是值相不相等

现在有一种特殊的情况就是

 例如 :

     String str1 = “Hello”;

     String str2 = "Hello";

     System.out.println("str1 == str2:" + (str1 == str2)); 显示为true

   为什么呢?可以这样解释,String常量是存放在常量池的,在程序编译期,对于str1,编译程序先去字符串常量池检查,是否存在“Hello”,不存在,则在常量池中开辟一个内存空间存放“Hello”;然后在栈中开辟一块空间,命名为“str1”,存放的值为常量池中“Hello”的内存地址, 到Str2,编译程序先去字符串常量池检查,是否存在“Hello”,一检查,发现有“Hello”,然后在栈中开辟一块空间,命名为“str2”,存放的值为常量池中“Hello”的内存地址, 所以str1和str2存放的内存地址是一样的

   

   因此知: String实质是字符数组,两个特点:1、该类不可被继承;2、不可变性(immutable)

    例如 String s1 = new String("myString") 和 String s1 = "myString"; 

       第一种方式通过关键字new定义过程:在程序编译期,编译程序先去字符串常量池检查,是否存在“myString”,如果不存在,则在常量池中开辟一个内存空间存放“myString”;如果存在的话,则不用重新开辟空间,保证常量池中只有一个“myString”常量,节省内存空                      间。然后在内存堆中开辟一块空间存放new出来的String实例,在栈中开辟一块空间,命名为“s1”,存放的值为堆中String实例的内存地址,这个过程就是将引用s1指向new出来的String实例

      第二种方式直接定义过程:在程序编译期,编译程序先去字符串常量池检查,是否存在“myString”,如果不存在,则在常量池中开辟一个内存空间存放“myString”;如果存在的话,则不用重新开辟空间。然后在栈中开辟一块空间,命名为“s1”,存放的值为常量池                            中“myString”的内存地址

另外:我需要提一下 String 和 StringBuffer

String s1 = "Hello";

System.out.println("s1.hashCode: " + s1.hashCode());  值为:s1.hashCode: 69609650

String s2 = s1;

s1 += "abcd";

System.out.println("s1.hashCode: " + s1.hashCode()); 值为:s1.hashCode:-1093921804

System.out.println("s1: " + s1); 值为: s1: Helloabcd

System.out.println("s2: " + s2); 值为:s2: Hello

s1和s2的值不一样,由于String是不可变的,s1 += "abcd",可理解为:对于已经存在的String对象的修改都是重新创建一个新的对象,然后把新的值保存进去.即这时s1的值变为新值Helloabcd,

存放的内存地址也变了

StringBuffer sb1 = new StringBuffer("Hello");

System.out.println("sb1.hashCode: " + sb1.hashCode());  值为:sb1.hashCode: 1765413668

StringBuffer sb2 = sb1;

sb1.append(" world!");

System.out.println("sb1.hashCode: " + sb1.hashCode());  值为:sb1.hashCode: 1765413668

System.out.println("sb1: " + sb1); 值为: s1: Hello world!

System.out.println("sb2: " + sb2); 值为:s2: Hello world!

StringBuffer 是一个可变对象,当对他进行修改的时候不会像String那样重新建立对象
           它只能通过构造函数来建立,
          StringBuffer sb = new StringBuffer("Hello");
          对象被建立以后,在内存中就会分配内存空间,并初始保存一个"Hello".通过它的append方法向其赋值.
          sb.append("world!"); 

sb2指向的是sb1指向的引用,它们指向同一个对象,操纵的也是同一个对象,通过它们得到的是同一个对象的内容

总结来说:

  1)对于==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;

    如果作用于引用类型的变量,则比较的是所指向的对象的地址

  2)对于equals方法,注意:equals方法不能作用于基本数据类型的变量

    如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;

    诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。

猜你喜欢

转载自www.cnblogs.com/tech-666/p/8992102.html