
你剛走出面試間手心還殘留著握筆的汗意。那些基礎題像回馬槍一樣殺回來——明明看過無數遍卻在脫口而出的瞬間卡殼或者自信滿滿地給出一個標準化答案卻被面試官追問得啞口無言。別急著怪自己臨場發揮失常基礎題的“坑”從來不在于知識本身而在于你對其“底層原理”的理解停留在背誦層面。這次復盤我們不聊高并發、不聊分布式就回到Java最樸素的語法和JVM基礎看看那些最容易答錯、也最暴露真實水平的問題。與equals你確定你懂“引用比較”嗎幾乎每個面試官都愛問“和equals的區別”但鮮少有人能撐過三輪追問。標準答案誰都會背比較地址equals比較內容。可一旦換成“String s1 new String(abc); String s2 abc; s1s2的結果”很多人就開始混亂。真正的分水嶺在于有沒有理解字符串常量池與堆內存的分配機制。new出來的對象在堆里字面量指向常量池兩者地址必然不同。但若繼續追問“為什么很多公司要求用String.equals而不是”你就得說清楚String重寫了equals的邏輯——先比較引用再比較類型最后逐字符比較數組。更隱蔽的錯誤來自基本類型包裝類。Integer a 127; Integer b 127; ab返回true但換成128就變成false。緩存機制的范圍是-128到127超出這個區間每次都會new新對象。這題答錯的人往往會把“自動裝箱”和“緩存”混為一談。面試官真正想聽的是你知道Integer內部有一個IntegerCache靜態類它預緩存了常用數值而這是JVM性能優化的一種手段。下次再遇到這題別光說結果要把緩存范圍、觸發條件、為什么設計這個范圍講透——基礎題的高分答案永遠是“原理場景設計動機”三位一體。String、StringBuilder、StringBuffer不可變性的連鎖考題“String為什么設計成不可變”這題答“因為final修飾”只能得20分。面試官想聽的是三個層次第一不可變性帶來線程安全無需同步第二字符串常量池可以復用降低內存開銷第三hashCode可以緩存適合作為HashMap的key。但最致命的追問是“那StringBuilder可變為什么不是線程安全的它和StringBuffer的區別僅僅在synchronized嗎”很多人答到“StringBuffer加了同步鎖”就停住了卻忽略了StringBuilder在單線程下性能優于StringBuffer是因為沒有鎖競爭的開銷而鎖不僅僅是方法級別還可能涉及偏向鎖、輕量級鎖的升級過程。更刁鉆的問題是“字符串拼接用到底做了什么”。在JDK8及以前編輯器會把“abc”優化成new StringBuilder().append(a).append(b).append(c).toString()所以循環內直接寫“str item”會不斷創建StringBuilder和String對象導致OOM風險。而在JDK9之后引入了invokedynamic和StringConcatFactory運行期才能真正優化。這題答錯的人往往還停留在“就是語法糖”的膚淺理解上沒意識到編譯優化和運行期優化的區別。面試官借此判斷你是否關心版本演進是否讀過JEP相關文檔。HashMap從存儲結構到擴容機制的連環陷阱HashMap是面試里的常青樹但基礎題陷阱密集。最經典的是“HashMap線程安全嗎為什么不安全”標準答案是“JDK7頭插法會形成環JDK8尾插法可能數據覆蓋”。但如果你把“put時modCount不是原子操作”和“resize時多個線程同時rehash導致數據丟失”也一并說出分數立刻拉開。安全問題的本質是復合操作的非原子性而不是單步操作的問題。再考一題“HashMap在JDK8中為什么要先比較hash再比較equals”很多人背過“先hash后equals”卻說不清原因。因為hashCode定位桶同一個桶內可能有多個鍵值對先用hash篩選可以減少equals調用次數——當hash不同時對象一定不相等但hash相同時對象不一定相等哈希沖突。所以equals相等的兩個對象必須有相同的hashCode但hashCode相同的對象equals不一定相等。這是Java規范硬性要求也是HashMap正確運作的前提。你若答不出這層邏輯面試官會懷疑你連對象比較的基本契約都沒掌握。接著問“HashMap的擴容為什么是2的冪次方”多數人答“為了用位運算取模”。但更深的細節是“當舊容量是16元素在新數組的下標要么在原位置要么在原位置16這個規律怎么來的”這涉及rehash時對hash值高位參與運算的理解。JDK8的擴容不需要重新計算hash只需看原hash值新增的那一位是0還是1是0則下標不變是1則下標加舊容量。這個設計精妙且高效而你沒讀過源碼根本答不出來。基礎題復盤的意義就在于此考察的不是你背過多少結論而是你能否還原源碼中的每一步設計決策。線程與鎖synchronized和volatile的認知邊界“volatile能保證原子性嗎”這恐怕是基礎錯題里的重災區。上來就答“volatile保證可見性和有序性不保證原子性”的人會被追問“那i用volatile修飾會怎樣”正確回答是“i分三步讀-改-寫volatile無法保證三步連續執行所以結果可能小于期望值”。但如果你繼續深入“volatile是如何禁止重排序的”就需要提到內存屏障——在每個volatile寫操作前插入StoreStore屏障在寫后插入StoreLoad屏障讀操作后插入LoadLoad和LoadStore屏障。能報出這四種屏障名稱和插入位置的人鳳毛麟角。再看synchronized。面試官常問“synchronized鎖的是什么”普通方法鎖this靜態方法鎖Class對象代碼塊鎖指定對象。但更進階的問題是“為什么JDK6要引入偏向鎖和輕量級鎖”你要從鎖競爭的代價說起無競爭時直接CAS嘗試獲取輕量級鎖只有競爭激烈才升級為重量級鎖這是為了減少用戶態到內核態的切換開銷。鎖升級路徑無鎖→偏向鎖→輕量級鎖→重量級鎖是分析synchronized性能的關鍵。很多人把“鎖消除”和“鎖粗化”搞混——鎖消除是JIT檢測到不存在競爭時直接去掉鎖鎖粗化是把多個相鄰的鎖請求合并成大鎖塊。這兩個概念雖然不屬于基礎語法但面試官很愛挖坑因為它們在《深入理解Java虛擬機》里就有。異常體系checked與unchecked的哲學之爭“Java里什么異常可以不用捕獲”答案是RuntimeException及其子類以及Error。但很多人忽略了一個關鍵點Error也屬于unchecked但異常處理機制對待Error的態度是“程序無法恢復不應該捕獲”。面試官會追問“自定義異常應該繼承Exception還是RuntimeException”這里沒有絕對正確的答案但你要說出權衡如果繼承Exception調用方必須try-catch強制處理如果繼承RuntimeException調用方可以忽略適合用于非檢查型邏輯錯誤。在業務開發中80%的異常應該設計成RuntimeException因為強制檢查會讓方法簽名更臃腫且很多運行時異常根本沒法在編譯期預測。另一個高頻錯點是“finally里return了怎么辦”很多人知道“finally優先于catch中的return”但不清楚字節碼層面的機制。當catch里有returnfinally里有return時finally的return直接覆蓋掉catch的返回值。更極端的問題是“在try里System.exit(0)后finally還會執行嗎”如果面試官說“會”那你就踩坑了。System.exit(0)會終止當前運行的JVM無論后面有沒有finally都不會執行。涉及SecurityManager時還要考慮權限檢查但一般不會問到那么遠。這個基礎題背后的邏輯是想考察你對“程序終止”和“異常退出”語義的區分——只有退出虛擬機的調用才能阻斷finally。反射與代理為什么說反射很慢“反射為什么慢說說你優化的思路。”這題極易答空。籠統講“反射要解析類元數據動態調用”會顯得單薄。要拆解成三個層面第一反射調用方法時需要檢查方法權限、入參出參類型這比直接調用多了一堆NativeMethodAccessorImpl的本地調用第二方法調用要經過Method.invoke的包裝涉及可變參數裝箱、異常包裝第三JIT無法對反射調用進行內聯優化。真正能落到實踐上的優化是寫一個緩存策略把反射獲取的Method、Field緩存起來避免重復查找。或者更狠一點用MethodHandles.Lookup結合LambdaMetafactory生成調用點性能接近直接調用。面試官問這題是看你對“元編程”有沒有真實使用經驗而不是背名詞。還有個容易被忽視的錯點“Class.forName和ClassLoader.loadClass的區別”。forName會執行靜態初始化塊即觸發初始化而loadClass默認只做加載、連接不會初始化。這在寫JDBC驅動時很關鍵——Class.forName(com.mysql.jdbc.Driver)注冊驅動靠的就是靜態塊。如果你換成ClassLoader.loadClass驅動就沒注冊。一句話總結forName是“加載初始化”loadClass是“惰性加載”。放在基礎題復盤里這算是“背了API卻不知道副作用”的典型。集合比較Comparable與Comparator的時機選擇“一個類要排序實現Comparable好還是用Comparator好”這題看似簡單但很多人只答“Comparable是自然排序Comparator是自定義排序”沒有說清楚JDK8之后Comparator的lambda語義和鏈式調用。正確姿勢是如果這個排序是類的“內在屬性”比如Employee按工號排序就實現Comparable如果排序邏輯是臨時的、多變的比如同一批員工今天按年齡排、明天按工資排就用Comparator。更進階的是理解Comparator.comparing().thenComparing()的鏈式寫法以及對于null值處理的nullsFirst/nullsLast。面試官若追“為什么Comparator.compare方法要求o1和o2換位后結果取反”你要能說出“反對稱性”是排序算法正確性的前提——如果compare(a,b)0且compare(b,a)0那排序器會陷入混亂。此外TreeSet和TreeMap的排序依賴比較器但如果你把可變對象放入的話對象屬性變了卻未更新比較器邏輯會導致元素丟失。這正是“hashCode和equals影響HashSet而Comparable影響TreeSet”的對應關系——集合的根數據結構決定了它的去重和排序邏輯很多人只記住了HashSet忘了有序集合背后的比較契約。接口與抽象類從語法到設計意圖“接口和抽象類怎么選”這題必考答案模板是“語法上接口多實現抽象類單繼承語義上接口定義能力抽象類定義模板”。但多數人忽略了Java8之后接口有默認方法這打破了“接口只能有抽象方法”的舊印象。面試官也許會問“既然接口可以有default方法那抽象類還有什么存在意義”你要回答抽象類可以保存共享的成員變量、構造函數以及protected方法而接口的字段必須是public static final的默認值。更重要的是abstract class可以定義“模板方法”模式讓子類復用骨架流程而接口的default方法更適合做功能擴展和流式API。再深一層面試官可能讓你畫一個“類實現兩個接口它們有相同簽名default方法時怎么辦”你必須重寫該方法并手動指定調用哪個接口的default方法。這是語法層面的陷阱但很多人從沒寫過這種沖突。如果你能順帶提到“默認方法引入的菱形繼承問題可以用父類優先規則解決但接口間沖突必須顯式聲明”那就證明你真的思考過Java多繼承演進的邊界。內存模型與單例模式雙重檢查鎖為什么需要volatile單例模式幾乎是面試必寫代碼題而雙重檢查鎖DCL中的volatile是問得最多的地方。很多人能寫出volatile但說不出理由。正確答案instance new Singleton()不是原子操作它拆成三步——分配內存、初始化對象、把引用指向地址。在JIT指令重排序的影響下第三步可能先于第二步執行另一個線程此時訪問到未被初始化的半成品對象。volatile禁止了這第三步的重排序保證對象完全構造后再暴露引用。這個考點融合了JMM的happens-before規則、指令重排序、以及線程間共享變量的可見性是基礎題里含金量極高的一題。更激進的問法是“有沒有不用volatile的單例寫法”最優雅的是enum單例。因為JVM規范保證了枚舉類型的實例只能被創建一次且構造函數只能由JVM調用。枚舉單例不僅天然線程安全還解決了反序列化破壞單例的問題——普通類要實現Serializable就得重寫readResolve而枚舉壓根不需要。如果你能現場演示一個枚舉單例的獲取方式再對比懶漢雙重檢查鎖的代碼量面試官眼里會閃出“這是一個真正寫過生產代碼的人”的認可。基礎扎實才是高并發架構的底氣走完這十幾道題的復盤你會發現一個共性每一個看似簡單的“基礎題”背后都連接著JVM規范、源碼實現和并發理論。那些在面試中答錯的人多半是因為學習全靠“八股文”式記憶只記住了答案沒記住答案從何而來。而面試官真正在篩選的是那種能從“和equals”一路講到“JMM內存屏障”的候選者——因為只有這樣的人面對線上詭異的并發Bug、性能瓶頸時才有能力從底層原理出發推演問題而不是依賴百度。基礎題不是背誦題而是思維題。回到座位上把今天答錯的每一道題沿著“是什么-為什么-源碼怎么實現-設計動機是什么”這條鏈路重新梳理一遍。你不要期待下一次面試碰到原題而要期待每一個知識點都能伸出無數觸角連成一張網。網越密面試官越難用一句“深入談談”擊穿你。這份復盤就是你織網的起點。