1. 为什么字符串比较会踩坑刚学Java那会儿我最困惑的就是这段代码String a hello; String b new String(hello); System.out.println(a b); // false System.out.println(a.equals(b)); // true明明两个变量内容都是hello为什么比较的结果不一样后来在项目里还真因为这个踩过坑——用户登录时从数据库查出的密码和用户输入的密码用比较死活验证不通过。今天我们就彻底搞懂这个经典问题。关键点在于比较的是身份证equals()比较的是长相。就像双胞胎兄弟长得再像也是两个独立的人内存地址不同。Java中的字符串比较有三大陷阱字面量陷阱直接赋值的字符串会进入字符串常量池new对象陷阱每次new都会创建新对象编译优化陷阱拼接的字符串可能被编译器合并2. 和equals的本质区别2.1 操作符的真相在Java中是个直男它只关心两件事对基本类型int、char等直接比较值对引用类型如String比较内存地址int num1 10; int num2 10; System.out.println(num1 num2); // true String s1 new String(hello); String s2 new String(hello); System.out.println(s1 s2); // false2.2 equals的智慧equals()是Object类的方法默认行为和一样。但String类重写了这个方法让它变得聪明// String类中的equals实现简化版 public boolean equals(Object anObject) { if (this anObject) return true; if (anObject instanceof String) { String anotherString (String)anObject; // 逐个比较字符 for (int i 0; i value.length; i) { if (value[i] ! anotherString.value[i]) return false; } return true; } return false; }实测案例String s1 Java; String s2 Java; String s3 new String(Java); String s4 Ja va; System.out.println(s1.equals(s2)); // true System.out.println(s1.equals(s3)); // true System.out.println(s1 s4); // true编译器优化3. 字符串常量池的魔法3.1 什么是字符串常量池JVM有个特殊区域叫字符串常量池String Pool相当于一个字符串缓存中心。当使用字面量创建字符串时JVM先检查池中是否已有相同内容字符串如果存在直接返回引用如果不存在创建新字符串并放入池中String a hello; // 第一次创建放入池中 String b hello; // 直接使用池中的引用 System.out.println(a b); // true3.2 new String()的特殊性使用new创建字符串时会强制在堆内存新建对象String c new String(hello); // 创建两个对象 // 1. 先在常量池创建hello如果不存在 // 2. 再在堆中创建新对象内存结构示意图栈内存 堆内存 字符串常量池 a ──────────→ hello ←─────── b c ──────────→ [new String] 指向常量池的hello3.3 intern()方法的神奇作用手动将字符串放入常量池的方法String s1 new String(hello).intern(); String s2 hello; System.out.println(s1 s2); // trueintern()的工作流程检查常量池是否有相同内容字符串有则返回池中引用没有则添加当前字符串到池中4. 实战中的避坑指南4.1 推荐写法常量比较始终使用equals()userInput.equals(quit) // 正确 quit.equals(userInput) // 更安全防null忽略大小写比较Hello.equalsIgnoreCase(hello) // true使用Objects.equals()Java7Objects.equals(null, hello) // false不会抛NPE4.2 性能优化技巧避免重复创建字符串// 不好的写法 for (int i 0; i 1000; i) { String s new String(constant); // 创建1000个对象 } // 好的写法 String constant constant; for (int i 0; i 1000; i) { String s constant; // 复用同一个引用 }字符串拼接优化// 编译前 String s a b c; // 编译后编译器优化 String s abc;但如果是变量拼接String a a; String b a b; // 实际会new StringBuilder4.3 常见面试题解析问题1下面代码创建了几个对象String s new String(hello);答案1或2个。如果常量池没有hello先创建常量池对象再创建堆对象如果已有只创建堆对象。问题2以下输出结果是什么String s1 hello; String s2 he llo; System.out.println(s1 s2);答案true。编译器会优化为hello从常量池复用。问题3如何证明字符串是不可变的String s hello; s.toUpperCase(); // 返回新对象原字符串不变 System.out.println(s); // 仍输出hello5. 进阶字符串比较的底层实现5.1 JDK中的优化现代JDK如Java 17中String的equals()实现更加智能// Java 17的优化实现 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); }关键优化点先比较地址快速路径再比较编码格式Latin1/UTF16最后比较字节数组5.2 哈希码缓存机制String的hashCode()有缓存优化private int hash; // 默认为0 public int hashCode() { int h hash; if (h 0 value.length 0) { hash h computeHashCode(); } return h; }这使得多次调用hashCode()不会重复计算这也是为什么String常被用作HashMap的key。6. 特殊场景处理6.1 空指针安全比较// 传统写法可能NPE if (str.equals(value)) {...} // 安全写法 if (value.equals(str)) {...} // Java7推荐 if (Objects.equals(str, value)) {...}6.2 排序场景下的比较String[] names {Alice, Bob, alice}; Arrays.sort(names, String.CASE_INSENSITIVE_ORDER); // 排序结果Alice, alice, Bob6.3 敏感信息比较对于密码等敏感信息建议使用专门方法import java.security.MessageDigest; boolean isPasswordCorrect(String input) { return MessageDigest.isEqual( input.getBytes(), storedPassword.getBytes() ); }7. 最佳实践总结基础规则内容比较永远用equals()地址比较特殊场景才用性能敏感场景优先使用字面量创建字符串避免在循环中new String大文本比较先用length()过滤代码可读性使用Objects.equals()避免null检查常量放equals()左侧literal.equals(var)新版本特性Java 15的文本块...也遵循常量池规则Record类的字符串比较行为与String一致记住这个口诀字面量省内存new对象必不同比较内容用equals地址比较需慎重。在实际项目中我见过太多因为字符串比较不当导致的bug特别是分布式系统中不同节点生成的字符串即使内容相同用比较也会失败。