==和equals的区别

在我的认识中==和equals的区别可以简单概括为:==比较的是内存地址是否一致;equals比较的是对象的值是否一致。但是在深入了解后发现不能这么简单的进行区分。

==在比较时针对对数据类型分为两类情况:

1、比较基本数据类型时(整数、浮点、字符、布尔):比较的是值是否一致

2、比较引用数据类型时:比较的是内存地址是否一致

equals在比较时同样也需要分为两类情况:

首先需要知道,Java中所有的类都是继承于Object这个超类的,因此在Object类中,已经有定义过一个equals方法,可以看到该equals方法实际上也还是通过==实现的比较,比较的是对象的内存地址是否一致,一般情况下意义不大,因此在一些类库当中这个方法被重写过了,例如String、Integer、Date等。像比较常用的String的equals方法,可以看到在重写equals方法后比较的就是对象的值。

// Object.java
public boolean equals(Object obj) {
    return (this == obj);
}
// String.java
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

因此

1、如果equals方法没有被重写过:比较的内存地址是否一致

2、如果equals方法被重写过:按照被重写的equals方法进行比较,如String的equals,比较的就是对象的值

public class Test {
    public static void main(String args[]) {
        Person person1 = new Person("name", "age");
        Person person2 = new Person("name", "age");
        // 如果Person类中不重写equals方法,因为Object类中的equals方法比较的是内存地址是否一致,返回false
        // 如果Person类中重写了equals方法,那么按照重写的方法,比较的是Person对象中的name和age字段,返回true
        System.out.println(person1.equals(person2));
    }
}

public class Person {
    private String name;
    private String age;

    public Person(String name, String age) {
        this.name = name;
        this.age = age;
    }

    // 重写Object类中的equals方法
    @Override
    public boolean equals(Object obj){
        // 如果两个内存地址相同,那么一定是指向同一个对内存中的对象,就无需比较两个对象的属性值
        if(this == obj){
            return true;
        }

        // 判断obj是不是Person的一个对象
        // 如果是,做向下转型
        // 如果不是,直接返回false。
        if(!(obj instanceof Person)){
            return false;
        }

        // 比较Person对象中的name和age字段值是否一致
        Person person = (Person)obj;
        return this.name.equals(person.name) && this.age.equals(person.age);
    }

    // 重写equals方法时必须重写hashCode方法
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

上述代码中,如果没有在Person类中重写equals方法,那么比较时就会执行Object类中的equals方法,直接比较内存地址是否一致;如果有特定需求,那么就需要重写equals方法(重写equals方法时必须重写hashCode方法),此时再执行equals方法时就按照重写的逻辑进行比较判断。

public class Test {
    public static void main(String args[]) {
        String str1 = "str";
        String str2 = "str";
        String str3 = new String("str");
        String str4 = new String("str");
        str4 = str4.intern();

        System.out.println(str1 == str2); // true
        System.out.println(str1.equals(str2)); // true
        System.out.println(str1 == str3); // false
        System.out.println(str1.equals(str3)); // true
        System.out.println(str1 == str4); // true
        System.out.println(str1.equals(str4)); // true
    }
}

str1、str2指向的是同一个字符串对象,该字符串对象存放在常量池中,因此str1 == str2判断为true。

str3通过new String(“str”)创建对象,会先向常量池中添加一个”str”字符串对象( 如果常量池中“str”字符串不存在 ),然后在堆内存中新创建一个存储了“str”的String对象。str3指向这个堆内存中的String对象,str1指向常量池中的字符串对象,因此str1 == str3判断为false。

在JDK8中,String调用了intern()方法,如果常量池先前已创建出该字符串对象,则返回常量池中的该字符串的引用。否则,如果该字符串对象已存在与Java堆中,则将堆中对此对象的引用添加到字符串常量池中,并且返回该引用;如果堆中不存在,则在常量池中创建该字符串并返回其引用。

str4调用 intern方法时,因为常量池已经存在”str”字符串,所以直接返回该字符串的引用,此时str1、str4指向常量池中的字符串对象,因此str1 == str4判断为true。

另外:

1、通过String str = “str”方式创建对象,会将”str”字符串放入常量池(如果常量池中“str”字符串不存在),相当于创建了一个对象。

2、通过String str = new String(“str”)方式创建对象, 会将”str”字符串放入常量池(如果常量池中“str”字符串不存在) ,然后在堆内存中新创建一个存储了“str”的String对象 ,相当于创建了两个对象。

5 年 ago

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注