如何让equals()判定相等的Java对象占用同一内存地址?
如何让equals相等的Java对象复用同一内存实例?
这是个很实用的内存优化思路!咱们先把核心结论说清楚:没法直接让已经创建好的两个独立对象合并成一个,但可以通过对象池/缓存的方式,从源头保证equals相等的对象只被创建一次,后续全部复用同一个实例。
为什么不能直接合并已存在的对象?
Java里每个new出来的对象都是堆内存上的独立实例,对象的引用只是指向这些实例的指针。一旦t1和t2已经指向不同的实例,你没法手动修改内存地址让这两个实例合并,只能手动把其中一个引用指向另一个(比如t2 = t1),但原来的t2实例如果没有其他引用,会被GC回收——这是手动操作,没法自动处理所有相等对象。
正确的实现方式:对象缓存池
我们可以用一个缓存容器(比如ConcurrentHashMap)来存储已经创建的对象,每次需要创建对象时,先检查缓存里有没有equals相等的对象,有就直接返回已有引用,没有再创建新对象并存入缓存。
完整示例代码
首先必须正确重写equals()和hashCode()(这是缓存能工作的核心,因为HashMap依赖这两个方法判断对象是否相等),然后用静态工厂方法封装对象创建逻辑:
import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; public class Test { // 建议把属性设为final,做成不可变类,避免缓存对象被修改导致的问题 private final int x; private final String y; // 私有构造方法,强制通过工厂方法创建对象,避免外部直接new private Test(int x, String y) { this.x = x; this.y = y; } // 线程安全的缓存池,存储已创建的相等对象 private static final ConcurrentMap<Test, Test> INSTANCE_CACHE = new ConcurrentHashMap<>(); // 静态工厂方法:获取或创建相等的对象 public static Test getInstance(int x, String y) { Test candidate = new Test(x, y); // computeIfAbsent:如果缓存里没有这个对象,就把它放进去,否则返回已有的 return INSTANCE_CACHE.computeIfAbsent(candidate, k -> k); } // 重写equals,判断对象逻辑相等 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Test test = (Test) o; return x == test.x && Objects.equals(y, test.y); } // 重写hashCode,和equals保持一致(必须遵守:equals相等的对象hashCode必须相等) @Override public int hashCode() { return Objects.hash(x, y); } @Override public String toString() { return super.toString(); // 直接输出对象的内存地址标识 } public static void main(String[] args) { Test t1 = Test.getInstance(1, "T1"); Test t2 = Test.getInstance(1, "T1"); System.out.println("t1的地址:" + t1); System.out.println("t2的地址:" + t2); System.out.println("t1和t2是同一个实例吗?" + (t1 == t2)); // 输出true System.out.println("t1和t2equals相等吗?" + t1.equals(t2)); // 输出true } }
关键注意事项
- 必须重写equals和hashCode:这两个方法是缓存池工作的基础,一定要保证逻辑一致——equals相等的对象,hashCode必须相等,反之不一定(但尽量让hashCode分布均匀,减少冲突)。
- 优先做成不可变类:如果Test对象的属性可以被修改(比如有setter方法),那缓存的对象被修改后,它的equals和hashCode结果会变化,导致缓存失效甚至出现逻辑错误。所以建议把属性设为
final,不提供修改方法。 - 线程安全:示例里用
ConcurrentHashMap是为了保证多线程环境下的安全,如果是单线程场景,用普通HashMap也可以。
额外补充:类似的JVM优化
其实Java里的字符串常量池就是类似的思路:String s1 = "T1"; String s2 = "T1";,s1和s2会指向同一个字符串实例,这是JVM层面帮我们做的对象池优化。
内容的提问来源于stack exchange,提问作者Fabian Brandão
相关产品推荐
相关产品推荐

