Java如何比较两个字符串(对象)是否相等

深藏阁楼爱情的钟 2022-09-07 05:55 381阅读 0赞

看似简单的问题,可以引申为操作符==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的源码

  1. public boolean equals(Object obj) {
  2. return (this == obj);
  3. }

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

equals()源码

  1. public boolean equals(Object anObject) {
  2. if (this == anObject) {
  3. return true;
  4. }
  5. if (anObject instanceof String) {
  6. String aString = (String)anObject;
  7. if (coder() == aString.coder()) {
  8. return isLatin1() ? StringLatin1.equals(value, aString.value)
  9. : StringUTF16.equals(value, aString.value);
  10. }
  11. }
  12. return false;
  13. }

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

  1. public static boolean equals(byte[] value, byte[] other) {
  2. if (value.length == other.length) {
  3. for (int i = 0; i < value.length; i++) {
  4. if (value[i] != other[i]) {
  5. return false;
  6. }
  7. }
  8. return true;
  9. }
  10. return false;
  11. }

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

第一题:

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

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

第二题:

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

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

第三题:

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

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

第四题:

  1. "小萝莉" == "小萝莉"

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

第五题:

  1. "小萝莉" == "小" + "萝莉"

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

第六题:

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

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

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

1)Objects.equals()

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

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

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

  1. Objects.equals("小萝莉", new String("小" + "萝莉")) // --> true
  2. Objects.equals(null, new String("小" + "萝莉")); // --> false
  3. Objects.equals(null, null) // --> true
  4. String a = null;
  5. a.equals(new String("小" + "萝莉")); // throw exception

2)String 类的 .contentEquals()

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

  1. public boolean contentEquals(CharSequence cs) {
  2. // Argument is a StringBuffer, StringBuilder
  3. if (cs instanceof AbstractStringBuilder) {
  4. if (cs instanceof StringBuffer) {
  5. synchronized(cs) {
  6. return nonSyncContentEquals((AbstractStringBuilder)cs);
  7. }
  8. } else {
  9. return nonSyncContentEquals((AbstractStringBuilder)cs);
  10. }
  11. }
  12. // Argument is a String
  13. if (cs instanceof String) {
  14. return equals(cs);
  15. }
  16. // Argument is a generic CharSequence
  17. int n = cs.length();
  18. if (n != length()) {
  19. return false;
  20. }
  21. byte[] val = this.value;
  22. if (isLatin1()) {
  23. for (int i = 0; i < n; i++) {
  24. if ((val[i] & 0xff) != cs.charAt(i)) {
  25. return false;
  26. }
  27. }
  28. } else {
  29. if (!StringUTF16.contentEquals(val, cs, n)) {
  30. return false;
  31. }
  32. }
  33. return true;
  34. }

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

自定义对象的比较

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

  1. package com.study;
  2. import java.util.Objects;
  3. /** * @Description TODO * @Classname Person * @Date 2021/8/22 14:45 * @Created by 折腾的小飞 */
  4. public class Person {
  5. private String name; // 名字
  6. private int age; // 年龄
  7. // 无参构造
  8. public Person() {
  9. }
  10. // 有参构造
  11. public Person(String name, int age) {
  12. this.name = name;
  13. this.age = age;
  14. }
  15. // get方法和set方法,用来得到和设置成员变量的值
  16. public String getName() {
  17. return name;
  18. }
  19. public void setName(String name) {
  20. this.name = name;
  21. }
  22. public int getAge() {
  23. return age;
  24. }
  25. public void setAge(int age) {
  26. this.age = age;
  27. }
  28. // toString方法,用来打印
  29. @Override
  30. public String toString() {
  31. return "Person{" +
  32. "name='" + name + '\'' +
  33. ", age=" + age +
  34. '}';
  35. }
  36. }
  37. // 自定义对象的比较
  38. Person p1 = new Person("卓卓", 22);
  39. Person p2 = new Person("卓卓", 22);
  40. System.out.println(p1 == p2); // faslse
  41. System.out.println(p1.equals(p2)); //false

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

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

  1. public boolean equals(Object obj) {
  2. return (this == obj);
  3. }

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

  1. @Override
  2. public boolean equals(Object o) {
  3. if (this == o) return true;
  4. if (o == null || getClass() != o.getClass()) return false;
  5. Person person = (Person) o;
  6. return age == person.age &&
  7. Objects.equals(name, person.name);
  8. }

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

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

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

在这里插入图片描述
发现两个对象的内容相同,地址不同。
如果我们要比较内容的hashCode值呢?

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

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

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

发表评论

表情:
评论列表 (有 0 条评论,381人围观)

还没有评论,来说两句吧...

相关阅读

    相关 js比较对象是否相等

    > 前言:如何判断两个对象是否相等? 两个Object类型对象,即使拥有相同属性、相同值,当使用 == 或 === 进行比较时,也不认为他们相等。这就是因为他们是通过引用(内