看似简单的问题,可以引申为操作符==equals()方法有什么区别?

  • ==操作符用于比较两个对象的地址是否相等
  • equals()用于比较两个对象的内容是否相等
// String对象比较
String alita=new String("小萝莉");
String luolita=new String("小萝莉");
System.out.println(alita.equals(luolita)); // true
System.out.println(alita == luolita); // false

.equals() 输出的结果为 true,而“==”操作符输出的结果为 false
前者要求内容相等就可以,后者要求必须是同一个对象

Java 的所有类都默认地继承 Object 这个超类,该类有一个名为 .equals() 的方法。

Object的源码

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

Object 类的 .equals() 方法默认采用的是“==”操作符进行比较。
假如子类没有重写该方法的话,那么“==”操作符和 .equals() 方法的功效就完全一样——比较两个对象的内存地址是否相等。

equals()源码

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;
    }

首先,如果两个字符串对象的可以“==”,那就直接返回 true 了,因为这种情况下,字符串内容是必然相等的。否则就按照字符编码进行比较,分为 UTF16 和 Latin1,差别不是很大,就拿 Latin1 的来说吧。

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;
    }

我的 JDK 版本是 Java 11,也就是最新的 LTS(长期支持)版本。该版本中,String 类使用字节数组实现的,所以比较两个字符串的内容是否相等时,可以先比较字节数组的长度是否相等,不相等就直接返回 false;否则就遍历两个字符串的字节数组,只要有一个字节不相等,就返回 false。

第一题:

new String("小萝莉").equals("小萝莉")

.equals() 比较的是两个字符串对象的内容是否相等,所以结果为 true。

第二题:

new String("小萝莉") == "小萝莉"

==操作符左侧的是在堆中创建的对象,右侧是在字符串常量池中的对象,尽管内容相同,但内存地址不同,所以返回 false。

第三题:

new String("小萝莉") == new String("小萝莉")

new 出来的对象肯定是完全不同的内存地址,所以返回 false。

第四题:

"小萝莉" == "小萝莉"

字符串常量池中只会有一个相同内容的对象,所以返回 true

第五题:

"小萝莉" == "小" + "萝莉"

由于‘小’和‘萝莉’都在字符串常量池,所以编译器在遇到‘+’操作符的时候将其自动优化为“小萝莉”,所以返回 true。

第六题:

new String("小萝莉").intern() == "小萝莉"

new String(“小萝莉”) 在执行的时候,会先在字符串常量池中创建对象,然后再在堆中创建对象;执行 intern() 方法的时候发现字符串常量池中已经有了‘小萝莉’这个对象,所以就直接返回字符串常量池中的对象引用了,那再与字符串常量池中的‘小萝莉’比较,当然会返回 true

如果要进行两个字符串对象的内容比较,除了 .equals() 方法,还有其他两个可选的方案。

1)Objects.equals()

Objects.equals() 这个静态方法的优势在于不需要在调用之前判空。

public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}

如果直接使用 a.equals(b),则需要在调用之前对 a 进行判空,否则可能会抛出空指针 java.lang.NullPointerException

Objects.equals("小萝莉", new String("小" + "萝莉")) // --> true
Objects.equals(null, new String("小" + "萝莉")); // --> false
Objects.equals(null, null) // --> true

String a = null;
a.equals(new String("小" + "萝莉")); // throw exception

2)String 类的 .contentEquals()

.contentEquals() 的优势在于可以将字符串与任何的字符序列(StringBuffer、StringBuilder、String、CharSequence)进行比较。

public boolean contentEquals(CharSequence cs) {
    // Argument is a StringBuffer, StringBuilder
    if (cs instanceof AbstractStringBuilder) {
        if (cs instanceof StringBuffer) {
            synchronized(cs) {
                return nonSyncContentEquals((AbstractStringBuilder)cs);
            }
        } else {
            return nonSyncContentEquals((AbstractStringBuilder)cs);
        }
    }
    // Argument is a String
    if (cs instanceof String) {
        return equals(cs);
    }
    // Argument is a generic CharSequence
    int n = cs.length();
    if (n != length()) {
        return false;
    }
    byte[] val = this.value;
    if (isLatin1()) {
        for (int i = 0; i < n; i++) {
            if ((val[i] & 0xff) != cs.charAt(i)) {
                return false;
            }
        }
    } else {
        if (!StringUTF16.contentEquals(val, cs, n)) {
            return false;
        }
    }
    return true;
}

从源码上可以看得出,如果 cs 是 StringBuffer,该方法还会进行同步,非常的智能化;如果是 String 的话,其实调用的还是 equals() 方法。

自定义对象的比较

上面比较的String是系统自带的,下面我们来看看如果是自己定义类,使用equals还可以吗?

package com.study;

import java.util.Objects;

/**
 * @Description TODO
 * @Classname Person
 * @Date 2021/8/22 14:45
 * @Created by 折腾的小飞
 */
public class Person {
    private String name; // 名字
    private int age; // 年龄
	// 无参构造
    public Person() {
    }
	// 有参构造
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
	// get方法和set方法,用来得到和设置成员变量的值
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
	// toString方法,用来打印
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

// 自定义对象的比较
Person p1 = new Person("卓卓", 22);
Person p2 = new Person("卓卓", 22);
System.out.println(p1 == p2); // faslse
System.out.println(p1.equals(p2)); //false

怎么回事呢?
怎么使用了equals比较还是false呢?

ctrl+鼠标左键点击进去发现

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

当是同一个对象时,返回true;不是一个对象,返回false。
我们发现它还是比较的地址,怎么办呢?
我们需要重写equals()方法,让它去比较对象的内容
重写了的equals()方法如下:

@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }

第一个,判断,是否为同一个对象
第二个,判断传入的对象是否为空,类名是否相等
第三个,判断属性值是否相等

还有一个hashCode()方法,用于比较对象的hash值是否相同

System.out.println(p1.hashCode());
System.out.println(p2.hashCode());

JAVA比较两对象属性 java比较两个对象的内容_java


发现两个对象的内容相同,地址不同。

如果我们要比较内容的hashCode值呢?

我们也可以重写hashCode()方法,去比较对象内容的hash值

@Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

所以,比较自定义对象时,需要重写equals()hashCode()方法