ο髮嶒灤a全解析:從類到異常處理)
簡介這份資源是東北大學(xué)Java面向?qū)ο髮嶒炚n程第1至6次的完整代碼合集面向正在學(xué)習(xí)Java OOP的高校學(xué)生與自學(xué)者幫助解決實驗無從下手、缺少參考實現(xiàn)的問題。壓縮包共124個文件約51KB以java源碼與class字節(jié)碼為主輔以html說明頁面、dat數(shù)據(jù)文件、xml配置及少量gif與jar覆蓋類與對象、封裝、繼承、多態(tài)、構(gòu)造器、接口、異常處理、集合框架、文件流、數(shù)據(jù)結(jié)構(gòu)與設(shè)計模式等核心知識點從內(nèi)容預(yù)覽可見還包含GourmetCoffeeGUI等圖形界面與目錄加載相關(guān)類實驗場景較為完整。目前已有2002人學(xué)習(xí)下載說明該套代碼在課程學(xué)習(xí)者中認(rèn)可度較高。讀者可據(jù)此對照實驗要求梳理類結(jié)構(gòu)與方法調(diào)用關(guān)系理解私有變量與公共接口的封裝思路、子類對父類的擴展方式以及接口實現(xiàn)與多態(tài)調(diào)用并參考異常捕獲與集合存儲的寫法快速搭建可運行工程、排查編譯與運行錯誤為后續(xù)復(fù)雜Java應(yīng)用開發(fā)打下扎實基礎(chǔ)。1. 從六份 Java 實驗代碼里我拆出了面向?qū)ο笞钤摼毜膸准聨н^幾輪 Java 實驗的人都有個共識面向?qū)ο筮@東西光看語法是學(xué)不會的必須動手把類、繼承、多態(tài)、接口、集合、異常這些點一個個敲進(jìn)代碼里才能形成肌肉記憶。這份「Java 面向?qū)ο髮嶒炄繉嶒灤a 1 到 6」就是一套按實驗順序排好的完整代碼包覆蓋了從類與對象基礎(chǔ)、封裝繼承、多態(tài)與抽象類、接口與內(nèi)部類到集合框架、異常處理與文件 IO 的典型練習(xí)。它解決的不是「教你 Java 語法」的問題而是「給你一套能跑、能改、能對照的參考實現(xiàn)」讓你在做實驗卡殼時有個能翻的底本。適合正在上 Java 面向?qū)ο笳n、需要交實驗報告的學(xué)生也適合想系統(tǒng)補一遍 OOP 基本功的轉(zhuǎn)行者。下面我按實驗順序拆開講重點放在每個實驗的設(shè)計意圖、代碼結(jié)構(gòu)、參數(shù)怎么調(diào)以及我踩過的那些坑。2. 實驗 1 到 3類、封裝、繼承的代碼骨架怎么搭前三個實驗是整個面向?qū)ο蟮牡鼗芏嗳擞X得簡單就跳過結(jié)果后面寫多態(tài)和接口時連構(gòu)造方法鏈都理不清。這三個實驗的代碼量不大但每一行都在訓(xùn)練「把現(xiàn)實問題抽象成類」的思維。2.1 實驗 1類與對象的最小閉環(huán)實驗 1 通常要求定義一個簡單的類比如學(xué)生類或矩形類包含私有屬性、構(gòu)造方法、getter/setter再在 main 里創(chuàng)建對象并調(diào)用方法。這份代碼里用的是「學(xué)生成績管理」的場景核心結(jié)構(gòu)如下// Student.java public class Student { private String name; // 姓名私有屬性 private int score; // 成績私有屬性 // 無參構(gòu)造 public Student() { this(未知, 0); // 調(diào)用有參構(gòu)造避免重復(fù)賦值 } // 有參構(gòu)造 public Student(String name, int score) { this.name name; this.score score; } public String getName() { return name; } public void setName(String name) { this.name name; } public int getScore() { return score; } // 設(shè)置成績時做合法性校驗 public void setScore(int score) { if (score 0 || score 100) { throw new IllegalArgumentException(成績必須在 0-100 之間); } this.score score; } Override public String toString() { return Student{name name , score score }; } }這段代碼的關(guān)鍵點有三個一是this(未知, 0)這種構(gòu)造方法鏈能減少重復(fù)賦值邏輯二是setScore里加了范圍校驗這是封裝的意義——不讓外部隨意改壞內(nèi)部狀態(tài)三是重寫了toString()方便打印調(diào)試。參數(shù)方面score用int而不是String是為了后續(xù)排序和比較方便如果你實驗里要求成績帶小數(shù)改成double即可但要注意浮點數(shù)比較的精度問題。2.2 實驗 2繼承與方法重寫實驗 2 一般會引入繼承關(guān)系比如讓GraduateStudent繼承Student增加導(dǎo)師、研究方向等字段。這份代碼里的做法是// GraduateStudent.java public class GraduateStudent extends Student { private String supervisor; // 導(dǎo)師 private String researchArea; // 研究方向 public GraduateStudent(String name, int score, String supervisor, String researchArea) { super(name, score); // 必須先調(diào)用父類構(gòu)造 this.supervisor supervisor; this.researchArea researchArea; } Override public String toString() { return super.toString() , supervisor supervisor ; } }這里最容易翻車的地方是super(name, score)的位置——它必須是子類構(gòu)造方法的第一條語句否則編譯不過。另外如果父類沒有無參構(gòu)造子類就必須顯式調(diào)用super(...)這個規(guī)則在實驗 3 里會反復(fù)出現(xiàn)。參數(shù)上supervisor和researchArea用String存儲如果實驗要求關(guān)聯(lián)到某個導(dǎo)師對象那就得再定義一個Teacher類把這里改成Teacher supervisor這就是從「字符串建模」到「對象建?!沟纳?。2.3 實驗 3多態(tài)與抽象類的落地實驗 3 通常要求把父類改成抽象類或者定義抽象方法讓不同子類給出不同實現(xiàn)。這份代碼里把Student改成了抽象類增加了一個抽象方法getDescription()// Student.java 改為抽象類 public abstract class Student { protected String name; protected int score; public Student(String name, int score) { this.name name; this.score score; } // 抽象方法子類必須實現(xiàn) public abstract String getDescription(); public int getScore() { return score; } }然后在GraduateStudent和UndergraduateStudent里分別實現(xiàn)// UndergraduateStudent.java public class UndergraduateStudent extends Student { private String major; public UndergraduateStudent(String name, int score, String major) { super(name, score); this.major major; } Override public String getDescription() { return 本科生 name 專業(yè) major; } }多態(tài)的價值在這里體現(xiàn)得很直接你可以寫一個ListStudent往里放本科生和研究生遍歷時調(diào)用getDescription()運行時會自動找到對應(yīng)子類的實現(xiàn)。參數(shù)上要注意抽象類不能直接new必須通過子類實例化如果某個子類沒實現(xiàn)抽象方法編譯器會直接報錯這是好事能提前暴露遺漏。提示實驗 1 到 3 的代碼建議按順序跑一遍每跑完一個實驗就手動改幾個參數(shù)比如把成績改成負(fù)數(shù)看校驗是否生效把super調(diào)用刪掉看編譯報錯這種「故意改壞」的練習(xí)比只看代碼有效得多。3. 實驗 4 到 5接口、內(nèi)部類與集合框架的配合實驗 4 和 5 開始進(jìn)入 Java 面向?qū)ο罄锔腹こ袒沟牟糠帧涌诙x契約、內(nèi)部類簡化回調(diào)、集合管理批量數(shù)據(jù)。這三個東西單獨學(xué)都不難但放在一起用的時候初學(xué)者很容易寫出「接口當(dāng)類用、內(nèi)部類亂嵌套、集合遍歷時刪元素」這類問題。3.1 實驗 4接口與內(nèi)部類的典型寫法實驗 4 一般要求定義一個接口比如Sortable或Comparable的自定義版本然后讓某個類實現(xiàn)它。這份代碼里定義了一個Scorable接口// Scorable.java public interface Scorable { int getScore(); default boolean isPass() { return getScore() 60; // 默認(rèn)方法子類可覆蓋 } }然后Student實現(xiàn)這個接口并在內(nèi)部用一個靜態(tài)內(nèi)部類做比較器// Student.java 片段 public class Student implements Scorable, ComparableStudent { private String name; private int score; // ... 構(gòu)造和 getter 省略 Override public int getScore() { return score; } Override public int compareTo(Student other) { return Integer.compare(this.score, other.score); } // 靜態(tài)內(nèi)部類按姓名排序的比較器 public static class NameComparator implements ComparatorStudent { Override public int compare(Student s1, Student s2) { return s1.name.compareTo(s2.name); } } }這里有兩個關(guān)鍵設(shè)計一是default方法讓接口有了默認(rèn)實現(xiàn)子類不必強制重寫二是靜態(tài)內(nèi)部類NameComparator不持有外部類實例適合做工具型比較器。參數(shù)上Integer.compare比手寫this.score - other.score更安全因為后者在極端值下可能溢出。如果你實驗里要求匿名內(nèi)部類可以把NameComparator改成new ComparatorStudent() { ... }的形式效果一樣只是不能復(fù)用。3.2 實驗 5集合框架的增刪改查與遍歷實驗 5 通常要求用ArrayList、HashMap或HashSet管理一批學(xué)生數(shù)據(jù)并實現(xiàn)按條件篩選、排序、統(tǒng)計。這份代碼里用的是ArrayList加HashMap的組合import java.util.*; public class StudentManager { private ListStudent students new ArrayList(); private MapString, Student indexByName new HashMap(); public void addStudent(Student s) { students.add(s); indexByName.put(s.getName(), s); // 建立姓名索引 } // 按成績篩選 public ListStudent filterByScore(int minScore) { ListStudent result new ArrayList(); for (Student s : students) { if (s.getScore() minScore) { result.add(s); } } return result; } // 按成績排序 public void sortByScore() { Collections.sort(students); // 依賴 Comparable 實現(xiàn) } // 按姓名排序 public void sortByName() { students.sort(new Student.NameComparator()); } }這段代碼里最值得說的是indexByName這個HashMap——它讓按姓名查找從 O(n) 降到 O(1)這是集合選型的實際意義。參數(shù)上filterByScore的minScore是包含邊界的如果你要改成不包含把改成即可。另外Collections.sort和List.sort的區(qū)別在于前者是靜態(tài)工具方法后者是 Java 8 引入的默認(rèn)方法兩者底層都是歸并排序穩(wěn)定性有保證。注意遍歷ArrayList時如果同時刪除元素必須用Iterator.remove()或removeIf()直接調(diào)list.remove()會拋ConcurrentModificationException這是實驗 5 里最常見的翻車點。4. 實驗 6異常處理與文件 IO 的完整鏈路實驗 6 通常是綜合練習(xí)要求把前面定義的學(xué)生對象寫入文件再從文件讀回來同時處理各種異常。這份代碼里用的是「文本文件 緩沖流」的方案沒有上序列化因為實驗環(huán)境里更看重對try-catch-finally和try-with-resources的掌握。4.1 自定義異常與校驗邏輯先定義一個業(yè)務(wù)異常用來區(qū)分「文件格式錯誤」和「IO 錯誤」// InvalidStudentFormatException.java public class InvalidStudentFormatException extends Exception { public InvalidStudentFormatException(String message) { super(message); } }然后在解析每一行時拋出private Student parseLine(String line) throws InvalidStudentFormatException { String[] parts line.split(,); if (parts.length ! 2) { throw new InvalidStudentFormatException(字段數(shù)量不對 line); } try { String name parts[0].trim(); int score Integer.parseInt(parts[1].trim()); return new Student(name, score); } catch (NumberFormatException e) { throw new InvalidStudentFormatException(成績不是數(shù)字 line); } }這里的設(shè)計意圖是NumberFormatException是運行時異常直接往外拋會讓調(diào)用方難以區(qū)分「格式問題」和「文件不存在」包一層自定義異常后上層就能用catch (InvalidStudentFormatException e)單獨處理。4.2 文件讀寫與資源關(guān)閉寫文件和讀文件都用try-with-resources避免忘記關(guān)流// 寫入 public void saveToFile(String path) throws IOException { try (BufferedWriter writer new BufferedWriter(new FileWriter(path))) { for (Student s : students) { writer.write(s.getName() , s.getScore()); writer.newLine(); } } } // 讀取 public void loadFromFile(String path) throws IOException, InvalidStudentFormatException { try (BufferedReader reader new BufferedReader(new FileReader(path))) { String line; while ((line reader.readLine()) ! null) { if (line.trim().isEmpty()) continue; // 跳過空行 students.add(parseLine(line)); } } }參數(shù)上FileWriter默認(rèn)是覆蓋模式如果要追加需要傳第二個參數(shù)true。BufferedReader的readLine()返回null表示文件結(jié)束這個判斷不能少。另外文件路徑建議用Paths.get(data, students.txt)這種跨平臺寫法直接拼/在 Windows 上雖然也能跑但不夠規(guī)范。提示實驗 6 跑完后手動把數(shù)據(jù)文件里的某一行改成「張三,abc」再運行讀取程序看自定義異常是否被正確捕獲并打印出可讀信息。這個驗證步驟能幫你確認(rèn)異常鏈路是通的。5. 六個實驗跑下來這幾個坑我替你踩過了5.1 構(gòu)造方法鏈斷裂導(dǎo)致編譯失敗現(xiàn)象子類構(gòu)造方法里寫了super(name, score)但父類只有無參構(gòu)造編譯器報「無法將類中的構(gòu)造器應(yīng)用到給定類型」。原因Java 規(guī)定子類構(gòu)造必須調(diào)用父類某個構(gòu)造如果父類沒有無參構(gòu)造子類必須顯式super(...)且必須是第一句。解決要么給父類補一個無參構(gòu)造要么在子類第一行寫super(...)。我一般建議父類保留無參構(gòu)造減少子類負(fù)擔(dān)。5.2 重寫 equals 沒重寫 hashCode現(xiàn)象把Student對象放進(jìn)HashSet后明明兩個對象屬性一樣卻都被保留沒去重。原因HashSet先用hashCode定位桶再用equals比較。只重寫equals不重寫hashCode兩個相等對象的哈希值不同會被分到不同桶。解決用 IDE 自動生成或手寫hashCode確保equals為 true 的兩個對象hashCode也相等。實驗 5 里如果用到HashSet這個坑必踩。5.3 遍歷集合時刪除元素拋異?,F(xiàn)象for (Student s : students) { if (s.getScore() 60) students.remove(s); }運行時報ConcurrentModificationException。原因增強 for 循環(huán)底層用Iterator但remove調(diào)的是集合自己的方法導(dǎo)致迭代器狀態(tài)和集合狀態(tài)不一致。解決改用Iterator.remove()或者直接用students.removeIf(s - s.getScore() 60)后者更簡潔。5.4 文件路徑寫死導(dǎo)致?lián)Q環(huán)境就找不到文件現(xiàn)象在本機跑得好好的換臺機器或換個目錄就報FileNotFoundException。原因用了絕對路徑如D:/code/data.txt或者用了相對路徑但工作目錄變了。解決用Paths.get(System.getProperty(user.dir), data, students.txt)獲取當(dāng)前工作目錄下的路徑或者把文件放在src同級目錄并用相對路徑。實驗 6 里建議把數(shù)據(jù)文件放在項目根目錄的data文件夾下。5.5 抽象類里寫了構(gòu)造方法卻以為不能有現(xiàn)象看到抽象類里有構(gòu)造方法以為寫錯了刪掉后子類編譯報錯。原因抽象類可以有構(gòu)造方法只是不能直接new它的構(gòu)造方法是給子類super()調(diào)用的。解決保留抽象類的構(gòu)造方法用來初始化父類字段。實驗 3 里如果父類有name和score就應(yīng)該在抽象類構(gòu)造里賦值子類通過super(name, score)傳遞。6. 把實驗代碼改成自己的項目三個進(jìn)階改法這套代碼如果只是跑一遍交作業(yè)有點浪費。我一般會做三件事把它變成能寫進(jìn)簡歷的小項目。第一把Student換成你熟悉的業(yè)務(wù)對象比如「訂單」「商品」「傳感器讀數(shù)」然后重新設(shè)計字段和校驗規(guī)則。這一步練的是抽象能力——同樣的繼承、接口、集合結(jié)構(gòu)換一個領(lǐng)域能不能快速套上去。第二給StudentManager加一個命令行菜單用Scanner讀取用戶輸入實現(xiàn)增刪改查的交互。這個改動會逼你處理輸入校驗、循環(huán)控制、異常捕獲比單純寫類更有工程感。代碼結(jié)構(gòu)大概是這樣public static void main(String[] args) { StudentManager manager new StudentManager(); Scanner scanner new Scanner(System.in); while (true) { System.out.println(1.添加 2.按成績篩選 3.保存 4.讀取 0.退出); int choice scanner.nextInt(); scanner.nextLine(); // 吃掉換行符 switch (choice) { case 1: System.out.print(姓名); String name scanner.nextLine(); System.out.print(成績); int score scanner.nextInt(); manager.addStudent(new Student(name, score)); break; case 2: System.out.print(最低分); int min scanner.nextInt(); manager.filterByScore(min).forEach(System.out::println); break; case 3: try { manager.saveToFile(data/students.txt); } catch (IOException e) { System.out.println(保存失敗 e.getMessage()); } break; case 0: return; } } }第三把文件存儲從文本改成 JSON 或 CSV用第三方庫如 Jackson 或 OpenCSV 做序列化。這一步會接觸到依賴管理Maven/Gradle和庫的 API 設(shè)計是從「實驗代碼」到「真實項目」的關(guān)鍵一跳。參數(shù)上Jackson 的ObjectMapper默認(rèn)要求類有無參構(gòu)造和 getter/setter如果字段是private且沒有 getter需要加JsonProperty注解。從那以后我每次拿到一套實驗代碼都會先跑通、再改壞、最后換場景重寫一遍這三步走完代碼才真正變成自己的。希望幫到你。本文還有配套的精品資源點擊獲取