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