equals和==的区别

概括

equals==都是比较内存单元中存储的内容。
==可以用于基本类型和引用类型的比较。
equals只能用于引用类型的比较。
对于某些类比如String,对equals进行了重写,不再是比较内存单元中存储的内容。

==用于比较内存单元中存储的内容

1)基本类型范例

public static void main(String[] args) {
    int a =10;
    int b =10;
    System.out.println( a == b); // true
    b=50;
    System.out.println( a == b); // false
}

执行 int a =10;int b =10; 时,JVM在内存中为变量a分配存储单元并填入值10,为变量b分配存储单元并填入值10。在内存单元中,变量a和变量b占据不同的内存单元,但是内存单元存储的值都是10,是相同的,所以返回true。

      a               b

      │               │
      ▼               ▼                         
┌───┬───┬───┬───┬───┬───┬───┐
│   │10 │   │   │   │10 │   │
└───┴───┴───┴───┴───┴───┴───┘

执行 b=50; 时,JVM在把新的值50写入变量b的存储单元中,旧值被覆盖。此时变量a和变量b内存单元存储的值不相同,所以返回false。

      a               b

      │               │
      ▼               ▼                         
┌───┬───┬───┬───┬───┬───┬───┐
│   │10 │   │   │   │50 │   │
└───┴───┴───┴───┴───┴───┴───┘

2)引用类型范例

public static void main(String[] args) {
    String x="hello";
    String y= "HELLO";
    String z="hello";

    System.out.println( x == y.toLowerCase()); // false

    System.out.println( x == z); // true
}

对于引用类型,JVM在内存中为变量分配的存储单元存储的是引用类型的地址。

执行 String x="hello"; String y= "HELLO"; 时,JVM在内存单元中创建了两个字符串对象,然后为变量x和变量y分配两个存储单元,这两个存储单元存储的内容是两个字符串对象的内存地址。内存地址不同,所以比较结果为false。

          
                  ┌─────────────────────────────────────────┐     
                  │                                         │         
                  │                                         ▼     
┌───┬───┬───┬───┬─┴─┬───────┬───┬───────┬───┬───────┬───┬───────┬───┐
│   │ x │   │   │ y │       │   │"hello"│   │       │   │"HELLO"│   │
└───┴─┬─┴───┴───┴───┴───────┴───┴───────┴───┴───────┴───┴───────┴───┘
      │                             ▲
      └─────────────────────────────┘

执行 System.out.println( x == z); 结果为true,这涉及到了内存中的常量池。其实上面执行String x="hello"; String y= "HELLO";的时候都是先去常量池查找是否存在该字符串,如果常量池中没有才会创建字符串对象并返回该字符串对象的引用。所以在执行 String z="hello"; ,先去常量池查找,发现前面已经创建过 “hello” 这个字符串,所以直接把该字符串的引用返回给z。因此x和z存储的都是同一个字符串的 引用/内存地址 ,也就是x和z在内存单元中存储的内容相同,所以比较结果为true。

          
                  ┌─────────────────────────────────────────┐     
                  │                                         │         
                  │                                         ▼     
┌───┬───┬───┬───┬─┴─┬───────┬───┬───────┬───┬───────┬───┬───────┬───┐
│   │ x │   │   │ y │       │   │"hello"│   │       │ z │"HELLO"│   │
└───┴─┬─┴───┴───┴───┴───────┴───┴───────┴───┴───────┴─┬─┴───────┴───┘
      │                           ▲ ▲                 │
      └───────────────────────────│─┘                 │
                                  └───────────────────┘

equals用于比较内存单元中存储的内容

1)源码解析

equals 也是比较内存单元中存储的内容,不过只能用于引用类型的比较,不能用于基本类型。

其实查看源码很好理解,如下,equals方法本身是调用 == 方法,而 == 方法比较的是内存单元中存储的内容,所以equals方法也是用于比较内存单元中存储的内容。由于方法参数是Object类型,所以只能接受对象也就是引用类型。

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

2)demo程序

下面是demo程序,返回结果为false。

class Student{
    int age;
    String name;
    public Student(int age,String name){
        this.age = age;
        this.name = name;
    }
}
public class Hello {
    public static void main(String[] args) {
        Student stu1 = new Student(10,"张三");
        Student stu2 = new Student(20,"李四");
        System.out.println(stu1.equals(stu2));  // false
    }
}

被重写的 equals 方法

1)demo

public static void main(String[] args) {
    String x="hello";
    String y= new String("hello");  

    System.out.println( x.equals(y) ); // true
}

String y= new String("hello");通过new方法创建实例,就不会去常量池中查找是否已经存在 “hello” 字符串,而是创建一个新的字符串,并把新字符串的引用赋值给变量y。所以x和y存储的是两个字符串对象的 引用/内存地址,也就是变量x和变量y在内存单元中存储的内容是不同的。

虽然内存单元中存储的内容不同,但是String类对equals方法进行了重写,所以返回的结果并不是false。

2)analysis

String的equals方法源码实现如下:

private final byte coder;
static final boolean COMPACT_STRINGS;

/*
* 该函数用于获取coder属性的值
* 如果COMPACT_STRINGS为true,函数返回coder的值,否则coder默认值为UTF16
*/
byte coder() {
    return COMPACT_STRINGS ? coder : UTF16; 
}

private boolean isLatin1() {
    return COMPACT_STRINGS && coder == LATIN1;
}

... 

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String aString = (String)anObject;
        if (coder() == aString.coder()) {
            return isLatin1() ? StringLatin1.equals(value, aString.value)
      : StringUTF16.equals(value, aString.value);
        }
    }
    return false;
}

可以看到先使用 == 判断内存单元中存储的内容是否相同,由于x和y存储的是不同对象的内存地址,所以 this == anObject 不成立,但是源码中并没有直接 return false,而是继续向下判断。如果被比较的对象y不是字符串的实例直接返回false。如果对象y是字符串的实例,执行if语句块,就判断两个字符串的coder属性值是否相等,该属性的取值取值有两种可能: LATIN1或UTF16。

coder() == aString.coder() 等价于 this.coder() == aString.coder()

如果两个字符串的coder值一样,就继续执行。如果coder的值是LATIN1就执行StringLatin1.equals(value, aString.value) ,否则就执行StringUTF16.equals(value, aString.value)
注意这里的 StringLatin1.equals 方法说明了这里调用的equals方法被定义在StringLatin1类中(StringUTF16类对该方法的实现是一模一样的),源代码如下:

@HotSpotIntrinsicCandidate
public static boolean equals(byte[] value, byte[] other) {
    if (value.length == other.length) {
        for (int i = 0; i < value.length; i++) {
            if (value[i] != other[i]) {
                return false;
            }
        }
        return true;
    }
    return false;
}

从上面源码可以知道,如果coder相等那么就将两个字符串的长度进行比较,如果长度相等就逐个对比两个字符串的字符并返回结果。

小结
String类对equals进行了重写,比较 xy.toLowerCase()的过程:

  1. 比较内存单元存储的内容是否相同,由于x和y内存单元中存储的内存地址不同,继续判断;
  2. 对象是否是String类型的实例,x和y都是String类型的实例,继续判断;
  3. 判断这两个字符串长度是否相等,这两个字符串长度相等,继续判断;
  4. 判断这两个字符串的每个字符是否相等,结果是相等,所以返回true。

3)StringBuffer类与String无关,并没有对equals方法进行重写

StringBuffer类并没有对equals方法进行重写,查看可知调用的是Object原始的equals方法,比较的是内存单元的内容,内存单元存储的是两个字符串对象的 引用/内存地址,所以下面返回结果为false。

public static void main(String[] args) {
    StringBuffer stringBuffer1 = new StringBuffer("hello");
    StringBuffer stringBuffer2 = new StringBuffer("hello");
    System.out.println(stringBuffer1.equals(stringBuffer2)); // false
}

他の者にできたか?ここまでやれたか?この先できるか?いいや、仆にしかできない!

目录
×

喜欢就点赞,疼爱就打赏