1. 一道题的真实杀伤力:候选人当场破防
1.1 题面
先来看一道我这两年面试Java候选人时几乎必问的题,也是网上各种Java面试题合集里反复出现的经典。代码不长,就四行:
java复制String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s1.equals(s3));
请在心里先给出三个输出结果,然后继续往下看。
答案是:true、false、true。
1.2 现场反应与答案分布
这道题我大概面过几十个候选人,从刚毕业的校招生到号称五年经验的社招都遇到过。统计下来,能一次性完全答对的大概只有四成左右。剩下六成里,有直接说"输出false、false、true"的——这类是背过一点概念,但没理解;也有说"全是true"的——这类是纯粹的初学者思维,根本没区分==和equals;还有更离谱的,思考半天后告诉我"第一个可能是true也可能是false,取决于字符串池策略"。
最让我印象深刻的是一个小伙子,他明显背过八股文,脱口而出"==比较的是引用,equals比较的是内容",然后自信满满地给出了"false、false、true"的答案。我追问了一句:"那为什么第一个是false?你前面不是说==比较引用吗?"他愣住,然后开始支支吾吾。
这就是问题所在。很多人背下来的是结论,却没理解结论背后的机制。一旦面试官换个角度追问,或者代码场景稍作变化,立刻露馅。这篇文章我想把这套东西从头到尾掰开揉碎讲清楚——不仅告诉你是什么,还要告诉你为什么,以及面试官为什么要问这道题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. "=="比较的究竟是什么——从栈、堆和方法区说起
2.1 基本类型:比较的是数值本身
Java里有两类数据类型:基本类型(int、char、double、boolean等)和引用类型(所有new出来的对象)。
基本类型的变量,栈上直接存的就是值。比如:
java复制int a = 10;
int b = 10;
System.out.println(a == b); // true
这时候==比较的就是这两个变量栈帧里的数值,10 == 10,结果自然是true。这没什么好争论的。
但注意,即使是基本类型,也有个容易忽略的细节——float和double的==比较。浮点数因为精度问题,0.1 + 0.2 == 0.3是false,但这是另一个话题了。==本身在这里没做错什么,它只是忠实地比较了两个二进制表示的数值是否完全相同。这个分类一定要在脑子里装清楚:==用于基本类型时,比较的是值,这层意思没有任何歧义。
2.2 引用类型:比较的是"门牌号"
真正让无数人翻车的是引用类型。当变量是对象类型时,栈上存的不是对象本身,而是对象在堆内存中的地址——你可以把它想象成一个门牌号。==比较引用类型时,比较的是这个门牌号是否一样,也就是两个变量是否指向同一个内存地址,而不是门牌号对应的那套房子的装修是否一样。
举个例子:
java复制String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false
s1和s2分别new出了两个对象,它们在堆上占两块不同的内存,所以门牌号不同,==返回false。尽管两个对象内部的char[]内容都是"hello",但==根本不管这些,它只看门牌号。
这就是为什么网上经常有人说"==比较的是引用"——严格来说是"引用类型的变量,==比较的是引用指向的地址是否相同"。这个表述不完整,因为它没有涵盖基本数据类型的情况,但对于理解引用类型是够用的。
2.3 等号比较的本质:Java里没有"对象变量"这回事
很多初学者会踩一个概念上的坑:以为String s1 = "hello"中的s1就是对象本身。其实不是。Java里所有对象类型的变量,本质上都是引用(或者说指针,虽然Java不让你像C/C++那样操作指针,但机制是相通的)。
我在培训新人时常打一个比方:对象是停在停车场里的一辆车,变量是手上一把车钥匙,钥匙上刻着车位编号。==比较的不是车,而是钥匙上的车位编号是否一样。
两把钥匙如果刻着同一个车位号,那它们指向的是同一辆车——这就是true。两把钥匙刻着不同的车位号,哪怕停着两辆一模一样、连车牌都相同的车,==也是false。
当你写下s1 == s2时,Java虚拟机做的事情就是:取出s1这个变量里存的门牌号,取出s2里存的门牌号,然后比对两个号码是否相等。仅此而已。
理解了这一层,你就明白了为什么说"==比较地址"——因为引用类型变量本身存的就是地址。
