ttttwr

导航

Java中==和equals的区别

在 Java 中,我们判断两个变量是否相等时,时常会使用 == 号来判断。但是在一些情况下,使用 == 符号来判断二者是否相等时会遇到一些问题。

“==”符号的含义

  • 在判断基础类型是否“==”时,进行比较的是它们的数据值,例如 int、byte、long、double、boolean 等。
    • 在这里需要注意的是 int 这种类型是基础数据类型(也称为原始数据类型),但是它们对应的封装类,例如 Integer 则是引用类型而非基础类型,这到另一篇文章中再展开讨论。
  • 在判断引用类型是否“==”时,进行比较的是它们的堆内存地址。这也意味着如果我们写出如下的一段代码时,两次判断的结果是不同的:
String str_src = "Test";
String str_a = str_src.toLowerCase();
 // test
String str_b = str_src.toLowerCase();
 // test
System.out.println(str_a == str_b); // false
System.out.println(str_a.equals(str_b)); // true

equals 函数的含义

  • 我们知道,Java 中的类都继承于 Object 这个超类,实际上,equals函数就定义在 Object 类中,在 Object 类中,equals函数的作用和直接使用 “==” 符号一样是比较内存地址,但是一般在继承的类中这个方法都会被重写,例如我们找到 Java 中 String 类的 equals 函数的代码如下:
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    return (anObject instanceof String aString)
            && (!COMPACT_STRINGS || this.coder == aString.coder)
            && StringLatin1.equals(value, aString.value);
}
  • 可以看到,String 的 equals 函数实际上首先使用 “==” 比较了二者的内存地址是否相同,因为如果内存地址相同,显然其中保存的值也应该是相同的。然后对于不同地址的对象,它调用了 StringLatin1 这个类的 equals 函数来完成判断,因此我们继续阅读 StringLatin1 类的 equals 函数:
@IntrinsicCandidate
 // 这个注解代表该方法是候选方法,可以被更好(高效)的方法代替
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;
}
  • 这段代码相信大家都很熟悉了,也就是逐字比对 String 的值是否相同,这也就解释了为什么之前的代码中,使用 equals 函数才能正确判断两个字符串是否相等,这就是因为两次调用 toLowerCase 函数都新建了一个 String 对象,两个对象的值相同,但是内存地址不同,所有只有在逐字比对时才能得出相等的结论。

什么时候“==”能得到正确的结果?——也许是我们不希望的情况

在上面我们已经了解了,如果我们需要判断两个引用类型是否相等,应当使用 equals 函数,而不是“= =”。那我们就不禁想问了:为什么有的时候“= =”也能得到正确的结果呢?

  • 分析
    • 如果使用“==”能得出和 equals 函数相同的结果,那么一定是它们的逻辑有重合的点——二者在内存地址相同时都返回true
    • 也就是说,如果我们正在判断相同的两个变量本身的地址就是相同的,那么二者使用“==”或 equals 函数得到的结果都是相同的。
  • 结论
    1. 如果我们使用相同的常量为引用类型变量赋值时,获得的变量一定是“==”相等的,这是因为在编译时,相同的常量会被作为同一个,引用到一个相同的地址上。例如:
    String str_const1 = "Test";
    String str_const2 = "Test";
    System.out.println(str_const1 == str_const2); // true
    
    1. 当我们使用一个引用类型变量为另外一个赋值之后,这两个变量都将指向同一个内存地址。例如:
    String str_old = "Test";
    String str_new = str_old;
    System.out.println(str_old == str_new); // true
    
    1. 还有一种需要注意的情况,由于容易导致错误,因此单独提出来,其本质是2的一个特例——当返回值为引用类型却没有使用防御式拷贝时,两次获取的返回值实际上都指向了同一个变量,这也意味着如果修改其中的一个变量,另外一个变量的值实际上也会被修改(这里还涉及可变与不可变的问题,在下面的例子中,如果 name 的类型为 String 实际上不会导致修改到原变量的情况)。例如:
    import java.util.ArrayList;
    import java.util.List;
    
    public class testMain {
        public static class test{
            private final List<String> name;
            public test(List<String> name) {
                this.name = name;
            }
    
            public List<String> getName() {
                return name;
            }
        }
    
        public static void main(String[] args) {
            test a = new test(new ArrayList<>(List.of("a", "b")));
            var name_old = a.getName();
            System.out.println(name_old); // [a, b]
            var name_new = a.getName();
            System.out.println(name_new == name_old); // true
            name_old.add("c");
            System.out.println(name_new); // [a, b, c]
        }
    }
    

posted on 2024-04-29 17:04  TTTTWR  阅读(10)  评论(0)    收藏  举报