
簡介Thinking in Java第四版是Java程序員必讀經(jīng)典這份配套源碼覆蓋書中各章節(jié)示例從面向對象基礎到集合、多線程、網(wǎng)絡編程、IO與反射等高級特性一應俱全。壓縮包共2059個文件以817個Java源文件為主另含少量class、xml等輔助文件整體僅7.07MB導入IntelliJ IDEA即可直接運行調試省去手動建工程與配置的麻煩。目前已有961人學習下載適合正在研讀原書或希望結合源碼理解Java核心概念的讀者。通過運行這些示例可以直觀觀察封裝、繼承、多態(tài)、異常處理、容器與并發(fā)機制的實際實現(xiàn)還能借鑒作者對設計模式和代碼組織的處理方式對提升編碼功底與閱讀大型代碼能力都很有幫助。1. Thinking in Java 4 源碼包打開就能跑但先別急著雙擊我在接手一批 Java 基礎培訓材料時發(fā)現(xiàn)很多學習者卡在同一個地方書讀懂了例子看懂了一到自己敲代碼就崩。問題不在理解而在配套源碼的打開方式。這份《Thinking in Java 4》的 IDA 工程源碼包解決的就是“書里有代碼代碼在資源里資源在壓縮包里壓縮包解不開”這一整條斷鏈。它把第四版書里的幾百個示例整理成了可供直接運行的 IDEA 工程適合三類人剛讀完前三章想動手驗證的初學者、準備做 Java 知識梳理的在職開發(fā)者以及需要給學員準備可運行 Demo 的講師。下面我會拆開這個包的內(nèi)部結構、導入步驟、運行方式和我在實際使用中踩過的六個坑。2. 源碼包結構它到底裝了什么按什么邏輯組織2.1 目錄分章規(guī)則從第 1 章到第 21 章映射書里的章節(jié)順序這份源碼包在根目錄下按章節(jié)劃分每個章節(jié)目錄對應書中的一個章節(jié)命名方式直接采用章節(jié)主題詞。例如操作符章節(jié)、控制流程章節(jié)、復用類章節(jié)、多態(tài)章節(jié)、接口章節(jié)、內(nèi)部類章節(jié)、持有對象章節(jié)、異常章節(jié)、字符串章節(jié)、類型信息章節(jié)、泛型章節(jié)、數(shù)組章節(jié)、容器深入研究章節(jié)、I/O 章節(jié)等。每個章節(jié)目錄內(nèi)包含該章涉及的 .java 源碼文件文件命名與書中示例名稱保持一致。部分跨章節(jié)復用的工具類集中在根目錄的 net 包結構下這個包名對應的是原書作者提供的通用工具代碼包括打印輔助方法、文本文件讀取工具、目錄遍歷工具等。這個net包被多個章節(jié)的代碼引用所以在導入 IDEA 時需要注意不能只導入單個章節(jié)目錄只導一章會導致引不到工具類而滿屏紅錯。2.2 build.gradle 與源碼包組織方式Gradle 依賴一個 JDK其余全本地整個工程用 Gradle 做構建管理根目錄有 settings.gradle、build.gradle 和 gradle wrapper 配置。工程里對源碼包里的所有 .java 文件會被統(tǒng)一編譯章節(jié)之間的引用關系通過源碼目錄內(nèi)的 package 聲明來建立。因為代碼是 JDK 1.4/1.5 時代的寫法build.gradle 里用的是舊的 sourceSets 配置指定了 src 目錄為源碼根。plugins { id java } group thinking.in.java version 4.0.0 java { sourceCompatibility 1.8 targetCompatibility 1.8 } sourceSets { main { java { srcDirs [src] } } } tasks.withType(JavaCompile) { options.encoding UTF-8 }這份配置做了三件事把src目錄作為全部源碼的根目錄讓 IDEA 把src下所有的 .java 當做一個整體編譯單元把 Java 編譯級別降到 1.8保證老代碼不出語法兼容問題強制 UTF-8 編碼避免 Windows 默認 GBK 導致亂碼。這里的參數(shù)說明srcDirs [src]是核心如果源碼包物理結構不是src而是src/main/java需要改成對應路徑。要是你用的是 JDK 11 及以上版本sourceCompatibility可以保持 1.8但需要額外在 IDEA 里把 SDK 語言級別調成 8。2.3 為什么用 Gradle 而不是直接打開后手動建工程很多人習慣把 .java 文件直接拖進 IDEA 新工程里跑但這個包的體量和交叉引用決定了直接拖會出問題。我在某公司帶新人時就遇到過一個情況A 同學把某個章節(jié)目錄直接拖進空工程IDEA 提示缺少net.mindview.util.Print類他花了半小時以為是自己下載的包不完整后來才發(fā)現(xiàn)是沒把整個src目錄作為源碼根。正確做法是把整個源碼包當做一個 Gradle 工程導入讓 IDEA 通過 build.gradle 的 sourceSets 配置識別所有源碼。Gradle 的引入不僅僅是為了語法編譯更重要的是它解決了三個實際問題跨章節(jié)引用的自動編譯、JDK 版本的統(tǒng)一約束、以及 IDEA 對源碼目錄的自動標記。如果你不想用 Gradle純手動在 IDEA 里建一個空 Java 工程然后把src目錄標記為 Sources Root去掉默認的src/main/java結構效果也一樣。但用 Gradle 的好處是構建邏輯固化在配置文件里換電腦、換人接手都能一鍵同步。3. 導入 IDEA 的具體步驟從解壓到跑通第一個示例3.1 導入前檢查JDK 版本、IDEA 版本與 Gradle 版本匹配導入之前先做環(huán)境檢查我用的是 IDEA 2023.2 版本搭配 JDK 8 和 JDK 17 各一份。這個源碼包在 JDK 8 下最穩(wěn)定因為書里代碼不涉及新語法特性老代碼在 JDK 8 上編譯基本不會報錯。如果你本機只有 JDK 17也能跑但需要確認 build.gradle 里的sourceCompatibility已經(jīng)設為 1.8并且 IDEA 設置里的 Project SDK 選的是 17Language Level 選 8。這里有個坑JDK 17 的--add-opens規(guī)則更嚴格某些反射相關的示例運行會報 InaccessibleObjectException不過本書第四版的代碼用得不多可以忽略。檢查完版本后把整個源碼包解壓到一個沒有中文空格和特殊字符的路徑下。我曾經(jīng)給一個同事遠程調問題他解壓到C:\Users\張三\桌面\下載文件\Thinking in Java 4 源碼(2).zip對應的目錄下IDEA 的 Gradle 同步跑了很久最后失敗原因就是路徑里的空格和中文讓 Gradle wrapper 某些內(nèi)部腳本解析出錯。3.2 通過 Gradle 導入的完整操作路徑打開 IDEA選擇File - Open選中解壓后的根目錄注意不是選某個章節(jié)子目錄。IDEA 會識別到根目錄下的 build.gradle 文件自動彈出信任對話框選擇 Trust Project。接著 Gradle 開始同步需要等待下載 Gradle wrapper 對應的發(fā)行版具體版本看gradle/wrapper/gradle-wrapper.properties里的 distributionUrl。distributionBaseGRADLE_USER_HOME distributionPathwrapper/dists distributionUrlhttps\://services.gradle.org/distributions/gradle-7.6.4-bin.zip networkTimeout10000 validateDistributionUrltrue zipStoreBaseGRADLE_USER_HOME zipStorePathwrapper/dists上面這份是典型的 wrapper 配置distributionUrl 指向 Gradle 7.6.4。注意這個下載地址需要網(wǎng)絡能訪問外部的軟件源如果你的環(huán)境在內(nèi)網(wǎng)隔離就得手動下載對應版本的 Gradle 壓縮包放到本機 Gradle 目錄下否則同步會一直卡在 download 階段。網(wǎng)絡條件不好時我一般會直接在 IDEA 的Gradle JVM設置里選擇本地已安裝的 JDK并在settings.gradle里把插件倉庫切換到國內(nèi)鏡像源。3.3 手動方式不用 Gradle 時如何搭建頂層源碼根不用 Gradle 的導入方式也有適用場景。我是這樣做的IDEA 里新建一個 Java 空工程進入Project Structure - Modules把源碼包里的src目錄添加為 Sources 根目錄刪除工程自動生成的src/main/java目錄。操作路徑是File - Project Structure - Modules - Sources選中src目錄點Sources藍色按鈕。然后給工程添加一個運行配置選中代碼里的任意一個帶main方法的類右鍵 Run。只要src根目錄對整個工程可見所有章節(jié)的 package 和類引用都會正常編譯。這個方式少一步 Gradle 下載但代價是工程配置文件不歸 Gradle 管換機器后再同步比較煩。兩種方式里我推薦先用 Gradle 方式跑通一個示例保住全量編譯能力如果后續(xù)網(wǎng)絡有問題再切手動方式。4. 代碼運行驗證從打印練習到多態(tài)與泛型示例4.1 典型章節(jié)代碼的運行方式與輸出驗證選定運行入口類后右鍵 Run 即可。例如在操作符章節(jié)目錄下有一個顯示賦值和按位運算的 Demo它的 main 方法會打印一串運算輸出。運行前建議給 IDEA 設置一個統(tǒng)一的 VM 參數(shù)在 Run Configuration 的VM options填入-Dfile.encodingUTF-8和-Duser.languagezh。第一個參數(shù)保證所有輸出字符按 UTF-8 解碼第二個參數(shù)讓某些涉及日期的類輸出中文環(huán)境格式防止英文環(huán)境出現(xiàn)奇怪輸出。下面是一段典型的章節(jié)示例代碼屬于字符串操作章節(jié)里的正則處理場景import net.mindview.util.TextFile; import java.util.regex.Matcher; import java.util.regex.Pattern; public class RegexDemo { public static void main(String[] args) { String content TextFile.read(F:\\tmp\\test.txt); Pattern pattern Pattern.compile(\\b[A-Z]\\w); Matcher matcher pattern.matcher(content); while (matcher.find()) { System.out.println(匹配: matcher.group()); } } }這段代碼運行時TextFile.read()會從指定路徑讀取文本文件然后通過正則匹配提取所有大寫字母開頭的單詞。這里的關鍵在于TextFile類來自net.mindview.util包它的作用是封裝了文件讀取邏輯避免每章重復寫相同代碼。參數(shù)說明路徑必須是絕對路徑如果寫相對路徑容易定位到工程根目錄與執(zhí)行目錄不一致導致找不到文件。運行這段代碼前要確認net包在源碼根目錄下存在如果 IDEA 里看到TextFile標紅說明源碼根目錄沒配全回頭檢查src標記是否正確。4.2 持有對象與泛型章節(jié)把容器輸出從地址改成帶類型信息持有對象章節(jié)里有一段演示 HashMap 基本用法的代碼它的輸出結果在第四版書里會列出一串鍵值對。實際運行時如果你發(fā)現(xiàn)輸出順序和書上的截圖不一致不用慌。那年代代碼用的是散列結構輸出順序天然不保證穩(wěn)定書上截圖的順序只是當時一次運行的切片。import java.util.*; public class MapDemo { public static void main(String[] args) { MapString, Integer map new HashMap(); map.put(one, 1); map.put(two, 2); map.put(three, 3); for (Map.EntryString, Integer entry : map.entrySet()) { System.out.println(entry.getKey() - entry.getValue()); } } }這段代碼說明了三個重點泛型的String, Integer寫法來自源碼包里的示例變體它演示了泛型約束容器類型的理念entrySet()遍歷方式比keySet()再取 value 的做法少一次查找開銷這個小技巧是書里反復出現(xiàn)的慣用法HashMap不保證順序這是它的設計屬性不代表代碼有問題。當你把HashMap替換成LinkedHashMap輸出順序會變成插入順序這個改動對學習理解有直觀幫助。4.3 類型信息與反射示例在 JDK 版本差異下的行為表現(xiàn)類型信息章節(jié)里有幾個用反射打印類層次結構的例子它們會在運行時調用Class.forName()和getDeclaredMethods()等反射 API。在 JDK 8 下這類代碼沒有任何障礙直接運行。在 JDK 17 下要注意一個細節(jié)部分反射方法在訪問私有成員時會觸發(fā)封裝檢查但本書的示例都是在類自身的公開方法上調用反射所以實際很少踩到。以一段演示Class對象的例子為例當代碼嘗試打印某個類的所有方法時輸出列表順序在不同 JDK 下會有差異方法數(shù)組的順序不是規(guī)范定義的行為。如果你拿運行結果和書本對照發(fā)現(xiàn)次序不對可以確認這是反射 API 自身的返回順序不確定性不影響功能正確性。5. 避坑指南源碼包導入后最常見的六個問題與排查5.1 編譯紅標大面積出現(xiàn)原因通常是源碼根目錄標記錯誤現(xiàn)象IDEA 導入后工程里幾乎所有net包的引用文件都標紅多個章節(jié)代碼里的 import 語句報“Cannot resolve symbol”。原因你沒有把整個src目錄標記為 Sources Root或者用的是 Gradle 方式但srcDirs配置還沒生效。IDEA 默認不會自動把非標準目錄當成源碼根。請檢查Project Structure - Modules里src是否已經(jīng)標記為 Sources。特別容易出錯的是解壓后目錄里多了一層內(nèi)層目錄導致src在xxx/src而不是在根目錄直接叫src。解決打開Project Structure - Modules選中源文件目錄點擊藍色目錄圖標標記為 Sources。如果用的是 Gradle檢查build.gradle里srcDirs [src]路徑與實際解壓結構是否一致改完重新同步一次。從那以后我把源碼包導入順序固定為先確認目錄層級再標記源碼根最后跑第一個示例驗證。這套流程沒再翻過車。5.2 單個類運行報找不到或無法加載主類入口類與構建范圍問題現(xiàn)象右鍵某個類文件選擇 RunIDEA 報錯 “Error: Could not find or load main class”。原因這個類不在源碼根目錄的 package 路徑內(nèi)或者 IDEA 構建范圍只看當前 module而這個類所在的目錄沒被 module 識別。解決確認這個類確實在src目錄內(nèi)而且它有public static void main(String[] args)。如果類所在目錄沒被標記為 SourcesIDEA 不會編譯它。還有一種情況是類名與文件名不一致IDEA 的 Run 菜單會自動隱藏這類類文件你需要回到文件管理器確認實際文件名。5.3 Gradle 同步卡在下載依賴網(wǎng)絡受限或倉庫配置問題現(xiàn)象IDEA 右下角顯示 Gradle sync 正在進行十幾分鐘不結束日志停在Downloading gradle-7.6.4-bin.zip或插件依賴階段。原因wrapper 默認下載地址在海外內(nèi)網(wǎng)環(huán)境或者防火墻策略會阻斷這個請求。解決手動下載 Gradle 7.6.4 的壓縮包放到C:\Users\用戶名\.gradle\wrapper\dists\gradle-7.6.4-bin\哈希值\目錄下重啟同步。如果嫌路徑難找直接改distributionUrl指向本地路徑格式為distributionUrlfile:///D:/tools/gradle-7.6.4-bin.zip。改完保存后IDEA 會使用本地包。另一個問題是插件倉庫把settings.gradle里的倉庫加上國內(nèi)鏡像地址能明顯提速。5.4 控制臺中文亂碼編碼不一致導致輸出顯示異?,F(xiàn)象代碼里定義好的中文字符串運行后控制臺顯示為亂碼。原因sourceSets里的編譯編碼是 UTF-8但 IDEA 控制臺的輸出編碼跟隨系統(tǒng)默認值Windows 中文環(huán)境可能被設置為 GBK。解決在Help - Edit Custom VM Options里加上一行-Dfile.encodingUTF-8和-Dconsole.encodingUTF-8重啟 IDEA。同時把 Run Configuration 里的 VM options 填上-Dfile.encodingUTF-8。這兩處都要改只改一處有時還會出現(xiàn)編譯正常但運行亂碼的情況。5.5 示例代碼在 JDK 17 下運行報模塊訪問異常老代碼反射碰到強封裝現(xiàn)象部分反射相關示例在 JDK 17 下運行時報InaccessibleObjectException錯誤信息提示無法訪問某個類的內(nèi)部成員。原因JDK 17 默認啟用強封裝未顯式開放的模塊禁止被反射訪問內(nèi)部私有成員。這本書的代碼大部分在 JDK 8 時代編寫默認情況下訪問沒有問題的反射場景極少但如果有自定義類加載器或深反射就會觸發(fā)。解決首選方案是本機安裝 JDK 8并把 Project SDK 切到 JDK 8這是最省心做法。如果只能用 JDK 17在運行配置 VM options 里加--add-opens java.base/java.utilALL-UNNAMED一類的參數(shù)具體看報錯信息里提示的包路徑。我的習慣是直接準備兩個 JDK平時編譯用 17運行老代碼用 8切換成本低且不需要特判。5.6 運行結果與書上不一致輸出順序、空格、換行差異現(xiàn)象代碼能跑但輸出內(nèi)容與書本截圖有出入比如多了一行空輸出或者集合打印順序不同。原因第四版書本基于當時書稿錄制時的 JDK 版本容器類在后續(xù)版本里迭代了輸出格式和遍歷順序系統(tǒng)屬性方法如System.getProperties()的輸出內(nèi)容也會隨 JDK 版本變化。解決先檢查是不是編碼問題確認每行輸出是不是正常的可讀字符。再檢查 JDK 版本如果確實存在順序差異用代碼邏輯去理解而不是死記輸出。這本書的核心價值是原理和設計思路輸出文本只是輔助驗證。6. 進階用法把整章示例改造成自己的練習模板與驗證方法當你能穩(wěn)定跑通所有示例后真正的價值在于把這包源碼變成自己的實驗沙盤。我一般會做三個改造第一把net.mindview.util包里的工具類讀一遍理解作者為什么把打印、查找、目錄遍歷寫成靜態(tài)方法這能幫你建立寫通用工具類的習慣。第二給每個章節(jié)示例補上斷言把原來手動看控制臺輸出的方式升級成自動驗證。例如在一個雙冒號方法引用示例里把System.out.println(result)改成assert result.equals(預期值)配上 IDEA 的斷言開關運行失敗會直接標紅。第三在自己參與的項目里復用這些工具類。下面這行代碼摘自源碼包里的目錄遍歷工具類我抽取出來放在自己項目的通用模塊里public static TreeInfo walk(String start, String regex) { return recurseDirs(new File(start), regex); }這個walk方法接收一個起始路徑和一個正則表達式遞歸遍歷目錄返回匹配文件的樹形結構信息。參數(shù)說明start用絕對路徑regex匹配文件名模式如.*\\.java$匹配所有 Java 文件。它的價值在于幫你理解遞歸遍歷和文件過濾的組合寫法這在寫批量處理腳本時非常實用。我把這個方法改造成接收多個擴展名過濾參數(shù)在一鍵整理教學代碼時省了不少時間。驗證方法上我的建議是每讀完一章先從源碼包里抽出三個示例用自己話重寫一遍 main 方法再對照net包里的工具類減少重復代碼。寫完后跑一遍全文的編譯檢查確保整個src目錄無紅標、無報錯。這樣做有兩層收益一是熟悉了 IDEA 工程的全量構建流程二是把書上的示例真正轉變成了自己手下的代碼資產(chǎn)。最后說一個我自己的習慣從那以后每次拿到一個陌生源碼包我不會先雙擊“打開”而是先花三分鐘看目錄拓撲、找構建文件、確認代碼根位置這能避開絕大多數(shù)導入即紅的尷尬。這個習慣保持了很久讓我在處理各種書配源碼、課程 Demo 時都少走彎路。希望幫到你。本文還有配套的精品資源點擊獲取