在Java编程的日常工作中,字符串(String)无疑是最常用、也最重要的数据类型之一。从用户输入到数据库查询,字符串无处不在。然而,一个看似简单的操作——字符串比较,却常常隐藏着让人困惑的“陷阱”,尤其是对于初学者而言。
你是否还在用 == 运算符来比较两个字符串是否相同?如果是,那么这篇文章将带你深入理解Java字符串比较的奥秘,让你告别误区,写出更健壮、更专业的代码!

字符串比较的“陷阱”:双等号 ==

首先,我们来聊聊很多新手(甚至一些老手)都会犯的错误:使用 == 运算符来比较字符串的内容。
在Java中,== 运算符被设计用来比较基本数据类型(如 int, boolean, char 等)的值。但是,对于对象(包括 String 对象),== 比较的是两个引用变量是否指向内存中的同一个对象。换句话说,它判断的是两个变量是否是“同一个东西”,而不是“内容是否相同”。
想象一下,你有两份礼物,它们被包装在看起来一模一样的盒子中。如果你使用 == 来比较它们,你问的是:“这两个包装盒是同一个吗?”只有当这两个变量确实指向内存中的同一块地址时,== 才会返回 true。它并不关心盒子里的礼物是否一样。
让我们看一个例子:
JAVA
String s1 = "hello"; String s2 = "hello"; String s3 = new String("hello"); String s4 = new String("hello"); System.out.println(s1 == s2); // 输出:true System.out.println(s1 == s3); // 输出:false System.out.println(s3 == s4); // 输出:false
为什么 s1 == s2true,而 s1 == s3s3 == s4false 呢?
  1. s1s2:当你在代码中直接使用字符串字面量(如 "hello")时,Java会利用“字符串常量池”进行优化。如果常量池中已经存在相同内容的字符串,Java会直接返回已有的引用。因此,s1s2 实际上指向了内存中的同一个 "hello" 对象。
  2. s3s4new String("hello") 明确地创建了一个新的 String 对象,即使内容相同,它们也是不同的对象,拥有不同的内存地址。所以 s1s3 不同,s3s4 也不同。
显然,== 运算符通常不是我们比较字符串“内容”时想要的结果。

字符串比较的“主角”:equals() 方法

既然 == 不能用来比较字符串内容,那我们应该用什么呢?答案就是 String 类提供的 equals() 方法!
String 类重写了从 Object 类继承的 equals() 方法,用于比较字符串对象的实际内容(字符序列)是否相等。回到我们礼物的比喻,equals() 问的是:“这两个包装盒里的礼物是一样的吗?”

equals():区分大小写的精确比较

equals() 方法会逐个字符地比较两个字符串,并且区分大小写。只有当两个字符串的长度相同,且所有对应位置的字符都相同时,它才会返回 true
JAVA
String strA = "Java"; String strB = "Java"; String strC = "java"; // 注意:'j' 是小写 System.out.println(strA.equals(strB)); // 输出:true System.out.println(strA.equals(strC)); // 输出:false (因为大小写不同)

equalsIgnoreCase():不区分大小写的比较

有时候,我们并不关心字符串的大小写,只希望比较它们的内容是否相同。这时,String 类的 equalsIgnoreCase() 方法就派上用场了。它会忽略字符的大小写进行比较。
JAVA
String strA = "Java"; String strC = "java"; System.out.println(strA.equals(strC)); // 输出:false System.out.println(strA.equalsIgnoreCase(strC)); // 输出:true (忽略大小写后相同)

空值(null)的考验与安全实践

在使用 equals() 方法时,还有一个重要的点需要注意:空指针异常(NullPointerException
如果你尝试在一个 null 字符串上调用 equals() 方法,就会抛出 NullPointerException
JAVA
String maybeNullStr = null; String compareToStr = "hello"; // System.out.println(maybeNullStr.equals(compareToStr)); // 这行代码会抛出 NullPointerException!
为了避免这种情况,我们可以采取以下安全实践:
  1. 先检查 null:在调用 equals() 之前,先判断对象是否为 null
    JAVA
    String maybeNullStr = null; String compareToStr = "hello"; if (maybeNullStr != null && maybeNullStr.equals(compareToStr)) { System.out.println("字符串内容相同(且不是null)"); } else { System.out.println("字符串内容不同,或其中一个为null"); }
  2. 使用常量字符串调用 equals():如果你知道其中一个字符串肯定不是 null(例如,它是一个字面量常量),可以让它来调用 equals() 方法。
    JAVA
    String maybeNullStr = null; String compareToStr = "hello"; System.out.println("hello".equals(maybeNullStr)); // 输出:false,不会抛出异常 System.out.println("hello".equals(compareToStr)); // 输出:true
    这是因为 `