==与equals比较

96
玉圣
2019.04.10 17:18* 字数 3206
目录:
  • 一、比较
    • 自定义类型的比较
    • 系统类型的比较
      • String类的比较
      • 包装类的比较
  • 二、结论
    • ==的比较
    • equals的比较
    • 说明
结论:

先说结论:

  • ==
    基础类型变量:比较两个变量的 是否相等
    引用类型变量:比较的是两个变量在 堆中对象的存储地址 是否相等,即栈中的内容 是否相等。
    ---> 对象地址的比较
  • equals
    比较的是两个变量是否是对同一个对象的引用,即 堆中的内容 是否相等
    ---> 对象内容的比较
  • 说明:
    String类对equals 方法进行了重写,在比较String时,需要使用equals来比较是否相等。
    包装类中也对equals 方法进行了重写,比较时最好也使用此方法。

一、比较:

  • 说明:由于大部分类中都重写了equals方法,所以下面的从两方面来说:系统类的比较和自定义类的比较。

1、自定义类型的比较:

  • 先看例子:
       public class Person {
       
           String name;
       
           public Person(String name) {
               this.name = name;
           }
       
           public String getName() {
               return name;
           }
       
           public void setName(String name) {
               this.name = name;
           }
       
           /*
           @Override
           public boolean equals(Object obj) {
               if (!(obj instanceof Person)) {
                   return false;
               }
               return this.name.equals(((Person) obj).name);
           }
           */
       }
    
        public static void main(String[] args) {
            Person p1 = new Person("Tom");
            Person p2 = new Person("Tom");
      
            //false
            System.out.println("p1==p2? ==>" + (p1==p2));
             //equals方法重写之前:false
             //equals方法重写之后:true
            System.out.println("p1.equals(p2)? ==>" + (p1.equals(p2)));
        }
    

例子很简单,但凡懂Java/Android的人知道,用==比较这两个Person绝对是不等的,因为是new的两个对象嘛,好比你有两个女盆友:前任对象和现任对象,那绝对不是一个人!

而在重写equals方法之前,两个对象也是不等的,起始从Person类的父类Object中可以看到,equals方法中用的就是==的比较。

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

但当我重写了equals方法之后,就不一样了,两个new出来的对象是相等的!(难道我的前任女友和现任女友是一个人?姓名都不一样嘛!)

这就是系统中的很多类都重写equals 的原因,一般的,如果两个对象中的内容完全一样,就认为这两者是同一个对象,所以我在这里,认为两个名字相同的Person对象就是同一个。(当然,实际开发中根据情况而定)

2、系统类型的比较:

  • 说明:主要是针对String和包装类(如Integer等)来说。
2.1、String类的比较
  • 先看例子:

        public static void main(String[] args) {
            String a = "hello";
            String b = "hello";
    
            String c = new String("hello");
            String d = c.intern();
    
            //a==b?==>true
            System.out.println("a==b?==>" + (a==b));
            //a.equals(b)?==>true
            System.out.println("a.equals(b)?==>" + (a.equals(b)));
    
            //a==c?==>false
            System.out.println("a==c?==>" + (a==c));
            //a.equals(c)?==>true
            System.out.println("a.equals(c)?==>" + (a.equals(c)));
    
            //d==c?==>false
            System.out.println("d==c?==>" + (d==c));
            //d.equals(c)?==>true
            System.out.println("d.equals(c)?==>" + (d.equals(c)));
    
            //d==a?==>true
            System.out.println("d==a?==>" + (d==a));
            //d.equals(a)?==>true
            System.out.println("d.equals(a)?==>" + (d.equals(a)));
        }
    
  • 1、分析:

    • ab 指向同一个字符串,是相等的,这个母庸质疑。
    • ac== 比较是不等的,这是因为c 是通过new 出来的,所以会新申请一个内存地址,是一个新对象,所以其栈内存放的地址是和aa 的对象在常量池红) 不同的。
    • 再看最后两组比较,你会发现用== 比较,c是和d不等的,而a竟然和d相等,这个就涉及到intern方法的返回值含义了。insert方法是用来 返回字符串对象的规范化表现形式 的,具体在下面解释。
  • 2、概念:
    在得出结论之前,先说一下内存中的堆和栈,以及常量池。

    • 栈(Stack):存放 基本类型的数据对象的引用。对象本身并不存放在栈中。
    • 堆(Heap):存放由new创建的 对象和数组
    • 常量池(Constant Pool):JVM必须为每个被装载的类型维护一个常量池。
      常量池就是该类型所用到常量的一个有序集合,包括直接常量(基本类型、String)和对其他类型、字段和方法的符号引用。
  • 3、结论:
    了解了上面的概念,就好得出结论了,先看图:


    String内存赋值示意图
    • 第一步定义a的时候,会在常量池中放入字符串"hello"

    • 定义b的时候,由于常量池中已经有了字符串常量"hello",就直接指向了它。

    • 当创建字符串对象c的时候,会先去常量池中检查是否有字符串"hello",发现有了,就直接new 一个对象。
      这里注意了,为什么说要先去找常量池中是否有创建的字符串呢?如果ca 之前定义,则会多一步操作,就是发现常量池中没有创建的字符串,会先在常量池中创建一个字符串,然后在new 对象。这应该是为了共享。

    • 通过示意图和上面的结论可以得出:
      使用== 比较,比较两个是否相等。
      而由于String类重写了equals 方法,则不管是否是两个对象,只要其中的内容是相等的,就是相等的。

  • 4、面试题:String a = new String("hello"); 创建了几个对象?
    对于这个问题,就是我上面所分析的,创建几个对象,就要看其中的字符串(hello)是否存在于常量池中(准确的说是StringPool这个池子),如果常量池中没有这个字符串,则会先在常量池中创建一个字符串常量对象,然后在堆内存中new对象,那就是两个对象。否则会直接在堆内存中new对象,那就是一个对象。

2.2、包装类的比较
  • 先看例子:
    例子1:
        public static void main(String[] args) {
            Integer a = 127;
            Integer b = 127;
    
            Integer c = new Integer(127);
            int d = c.intValue();
            int e = 127;
    
            //a==b?==>true
            System.out.println("a==b?==>" + (a==b));
            //a.equals(b)?==>true
            System.out.println("a.equals(b)?==>" + (a.equals(b)));
    
            //a==c?==>false
            System.out.println("a==c?==>" + (a==c));
            //a.equals(c)?==>true
            System.out.println("a.equals(c)?==>" + (a.equals(c)));
    
            //d==c?==>true
            System.out.println("d==c?==>" + (d==c));
            //d==a?==>true
            System.out.println("d==a?==>" + (d==a));
            //e==c?==>true
            System.out.println("e==c?==>" + (e==c));
        }
    
    例子2:
        public static void main(String[] args) {
            Integer a = 128;
            Integer b = 128;
      
            Integer c = new Integer(128);
            int d = c.intValue();
            int e = 128;
      
            //a==b?==>false   <----看这里
            System.out.println("a==b?==>" + (a==b));
            //a.equals(b)?==>true
            System.out.println("a.equals(b)?==>" + (a.equals(b)));
    
            //a==c?==>false
            System.out.println("a==c?==>" + (a==c));
            //a.equals(c)?==>true
            System.out.println("a.equals(c)?==>" + (a.equals(c)));
    
            //d==c?==>true
            System.out.println("d==c?==>" + (d==c));
            //d==a?==>true
            System.out.println("d==a?==>" + (d==a));
            //e==c?==>true
            System.out.println("e==c?==>" + (e==c));
        }`
    

这里都是用int的包装类Integer 来说明的,其他的几种包装类都一样。

  • 1、分析:

    • 先说比较容易理解的,就是用==比较ac不相等,因为是两个对象,所以内存地址是不等的。

    • equals 比较的,都是相等的,这个是由于包装类Integer等都重写了Objectequals 方法,比较的是其中的值是否相等。

    • 从两个例子可以看出,a==b 的结果不同,是不是觉得很奇怪呢?这也是由于共享数据的原因,Integer共享的范围是-128 ~ 127。这个可以看一下Integer的源码中的valueOf方法。

    • 再看两个例子中的最后三个比较,intInteger 的比较,只要是值相等,就都是相等的。这里涉及到一个自动拆箱的过程。

  • 2、概念:

    • 共享数据:java为了优化内存,提高性能,就单开了一片内存池(pool),也就是说,在这里共享了那些固定不变的数据(我个人理解),如数字和字符串等等,这也是一种对象。

      重点说Integerint,在内存池中定义他们的范围是 -128 ~ 127,这里的数是共享的,其实共享的是地址,就是变量指向的地址。(题外话:变量其实都是指向的地址,地址才是代表一块内存的空间的。)java为了提高效率,初始化了-128--127之间的整数对象,所以,如果写Integer a =100;的话,是在内存池中创建了一个变量为a的对象,再写b=100,就共享了100这个数据,其实是指向了相同地址。但是如果超过了这个范围的话,这数据就不是共享的了,指向的不是相同地址。所以就相等了。

    • 自动封箱和自动拆箱:
      先看一个小栗子:

           public static void main(String [] args) {  
               Integer x = 4;  
               x = x + 2;  
               System.out.println("x" + x);  
           }  
      

      这里面就涉及到了自动拆箱和自动封箱的过程:
      1、自动封箱:其中Integer x = 4; 就是自动封箱的过程,即Integer x = new Integer(4);
      2、自动拆箱:其中x+2 就是进行了自动拆箱的过程,将x 变为int 类型,再和2进行加法运算,在拆箱的时候,要先进行 x.intValue() 的判断,是否xnull,不可为null ,否则编译失败。
      3、自动封箱:再将求的和进行封装,赋值给x

  • 3、结论:

    • 使用equals 比较的包装类,比较的是其中的 是否相等。
    • 使用== 比较的包装类,如果在共享范围内-128~127,则比较的是两个 是否相等。否则比较的是对象的 存储地址 是否相等。

二、结论

1、== 的比较:
  • 基础类型变量:比较两个变量的 是否相等
  • 引用类型变量:比较的是两个变量在 堆中对象的存储地址 是否相等,即 栈中的内容 是否相等。---> 对象地址的比较
2、equals的比较:
  • 比较的是两个变量是否是对同一个对象的引用,即 堆中的内容 是否相等。
    ---> 对象内容的比较
3、说明:
  • String类对equals 方法进行了重写,在比较String时,需要使用equals来比较是否相等。
  • 包装类中也对equals 方法进行了重写,比较时最好也使用此方法。
  • 对象的比较,基本都是用equals来比较是否相等。
  • 在自定义的类中,要依据情况来决定是否需要重写equals方法。
  • 如果无法判断两个对象(比如a和b)是否某一个为null时,可以通过Objects.equals(a, b) 来进行比较,其内部是对a 进行null 值判断,然后才比较的a.equals(b)
  • 在Java规范中说明,在比较两个枚举类型的值时,永远不需要调用equals 方法,而直接使用== 就可以了。
Java基础学习
Gupao