
簡介這份資源是東北大學Java面向對象實驗1至6的完整代碼合集面向正在修讀該課程的學生以及希望系統(tǒng)練習OOP核心語法的自學者可解決實驗無從下手、代碼結構混亂、知識點難以串聯(lián)等問題。壓縮包共124個文件約51KB以java源碼與class字節(jié)碼為主輔以html說明頁、dat數(shù)據(jù)文件、xml配置、gif圖示及少量jar包覆蓋類與對象、封裝、繼承、多態(tài)、構造器、接口、異常處理、集合框架、文件流、復雜數(shù)據(jù)結構與設計模式等實驗主題從內容預覽可見還包含GourmetCoffeeGUI等圖形界面與目錄加載相關類能直觀感受多類協(xié)作與事件監(jiān)聽的組織方式。目前已有2002人學習下載說明該實驗體系在課程學習者中認可度較高。讀者可據(jù)此對照實驗要求梳理每個實驗的類結構、方法職責與調用關系理解私有字段與公共接口的配合、父類子類擴展方式以及接口實現(xiàn)多態(tài)的具體寫法并借助dat與xml文件觀察數(shù)據(jù)持久化與配置讀取思路從而快速完成實驗并夯實面向對象編程基礎。1. 從一份 Java 實驗代碼說起六個實驗到底練了什么很多人學 Java 卡在“語法都會一寫就廢”的階段。課本上的for循環(huán)、extends、interface看得懂真讓你從零寫一個能跑的小系統(tǒng)腦子里卻一片空白。這份面向對象實驗代碼合集恰好就是沖著這個痛點來的——它把 Java 面向對象的核心知識點拆成六個遞進的實驗每個實驗都是一個能獨立編譯運行的小項目從類與對象、封裝繼承多態(tài)一路做到集合框架、異常處理和文件 IO。適合正在上 Java 課、需要交實驗報告的學生也適合想系統(tǒng)補一遍 OOP 基礎的自學者。代碼結構清晰注釋到位拿來當模板改一改就能交作業(yè)也能當復習提綱用。下面我按“能干什么 → 怎么跑 → 怎么改”的順序把這份資源拆開講。2. 實驗一到六的知識點分布每個實驗在練什么2.1 六個實驗的遞進關系這份代碼不是六個孤立的練習而是一條有依賴關系的知識鏈。實驗一通常從最簡單的類定義入手讓你寫一個Student或Circle類理解成員變量、構造方法、this關鍵字的用法。實驗二引入封裝把字段改成private配上getter/setter再加重載構造方法。實驗三進入繼承和多態(tài)一般會設計一個Shape抽象類派生出Circle、Rectangle用Override重寫area()方法。實驗四往往涉及接口和抽象類的區(qū)別比如定義一個Payable接口讓不同類去實現(xiàn)。實驗五開始碰集合框架用ArrayList、HashMap管理對象數(shù)據(jù)。實驗六綜合前面所有內容加上異常處理和文件讀寫做一個能存盤的小型管理系統(tǒng)。這種安排的好處是每個實驗只引入一兩個新概念不會一次性把你壓垮。我見過不少同學一上來就想寫完整系統(tǒng)結果類之間的關系理不清寫到一半就崩了。按這個順序走每步都有可運行的產出心里踏實。2.2 每個實驗的典型代碼結構以實驗三的圖形面積計算為例核心代碼大概長這樣// 抽象基類定義所有圖形的公共行為 abstract class Shape { private String name; public Shape(String name) { this.name name; } // 抽象方法子類必須實現(xiàn) public abstract double area(); public String getName() { return name; } } // 圓形子類重寫 area 方法 class Circle extends Shape { private double radius; public Circle(double radius) { super(圓形); this.radius radius; } Override public double area() { return Math.PI * radius * radius; } } // 矩形子類 class Rectangle extends Shape { private double width; private double height; public Rectangle(double width, double height) { super(矩形); this.width width; this.height height; } Override public double area() { return width * height; } }這段代碼的關鍵在于abstract和Override兩個點。Shape類本身不能被實例化它只負責定義“所有圖形都有面積”這個契約。子類各自實現(xiàn)area()調用的時候用父類引用指向子類對象運行時自動找到正確的方法——這就是多態(tài)。參數(shù)方面radius、width、height都是double類型保證計算精度。如果你把area()的返回類型改成int編譯不會報錯但結果會丟失小數(shù)部分這是新手常踩的坑。實驗五的集合操作通常是這樣組織的import java.util.*; public class StudentManager { // 用 ArrayList 存儲學生對象保持插入順序 private ListStudent students new ArrayList(); public void addStudent(Student s) { students.add(s); } // 按學號查找返回 Optional 避免空指針 public OptionalStudent findById(String id) { for (Student s : students) { if (s.getId().equals(id)) { return Optional.of(s); } } return Optional.empty(); } // 按成績降序排列 public void sortByScore() { students.sort((a, b) - Double.compare(b.getScore(), a.getScore())); } }這里用了List接口而不是直接寫ArrayList是為了后面換實現(xiàn)類方便。Optional是 Java 8 引入的用來顯式處理“可能找不到”的情況比返回null安全。排序用了 Lambda 表達式Double.compare處理浮點數(shù)比較比直接相減更可靠——直接相減在差值極小時可能返回 0導致排序不穩(wěn)定。2.3 知識點與實驗編號對照實驗編號核心知識點典型類/接口難度實驗一類與對象、構造方法Student、Circle入門實驗二封裝、方法重載BankAccount入門實驗三繼承、多態(tài)、抽象類Shape、Circle、Rectangle中等實驗四接口、內部類Payable、Comparator中等實驗五集合框架、泛型ArrayList、HashMap中等實驗六異常、文件 IO、綜合FileWriter、BufferedReader進階這張表可以幫你快速定位自己薄弱的部分。如果你已經(jīng)會寫類了直接從實驗三開始看如果集合框架不熟重點啃實驗五。3. 把代碼跑起來環(huán)境配置與編譯執(zhí)行3.1 JDK 安裝與環(huán)境變量這份代碼用的是標準 Java SE沒有依賴任何第三方庫所以只需要裝一個 JDK 就行。推薦 JDK 8 或 JDK 11這兩個版本在學校機房里最常見兼容性也最好。安裝完之后驗證一下# 檢查 JDK 版本確認安裝成功 java -version javac -version如果兩條命令都能輸出版本號說明環(huán)境沒問題。java是運行命令javac是編譯命令兩個都要有。有些同學只裝了 JRE結果javac用不了編譯都過不去。環(huán)境變量方面JAVA_HOME指向 JDK 安裝目錄PATH里加上%JAVA_HOME%\binWindows或$JAVA_HOME/binMac/Linux。配完之后最好重開一個終端窗口不然環(huán)境變量不生效。3.2 目錄結構與編譯方式代碼按實驗編號分目錄每個目錄下是獨立的.java文件。常見結構是experiment1/ ├── Student.java └── Main.java experiment2/ ├── BankAccount.java └── Main.java ...每個實驗目錄里都有一個Main.java作為入口里面包含public static void main(String[] args)方法。編譯的時候進入對應目錄# 進入實驗三目錄 cd experiment3 # 編譯所有 java 文件 javac *.java # 運行主類 java Mainjavac *.java會把當前目錄下所有 Java 文件一起編譯生成對應的.class文件。如果某個文件有編譯錯誤會直接報出來按行號去改就行。運行的時候寫java Main不要寫java Main.class后者會報錯。如果類在包里比如package com.exp3;編譯和運行都要帶上包路徑不過這份實驗代碼一般不帶包簡化處理。3.3 用 IDE 打開項目如果你習慣用 IDEIntelliJ IDEA 或 Eclipse 都可以直接打開。以 IDEA 為例File → Open選擇實驗目錄IDEA 會自動識別 Java 文件并配置 SDK。如果提示沒有 SDK去Project Structure → SDKs里添加你安裝的 JDK 路徑。運行的時候右鍵Main.java選Run Main.main()就行。用 IDE 的好處是調試方便可以在任意一行打斷點看變量當前的值。比如實驗三的多態(tài)調用你在area()方法里打個斷點運行的時候就能看到實際調用的是哪個子類的方法。這個對理解多態(tài)特別有幫助比光看代碼強得多。提示如果 IDEA 打開后代碼全是紅色報錯先檢查File → Project Structure → Project里的 Language Level 是不是設成了 8 以上。有些老版本默認是 5不支持 Lambda 和Optional。4. 核心模塊拆解從類設計到數(shù)據(jù)流轉4.1 實驗六的綜合管理系統(tǒng)架構實驗六通常是前面所有知識點的匯總做一個帶文件存儲的管理系統(tǒng)。它的類結構一般分三層實體類、管理類、入口類。實體類負責存數(shù)據(jù)管理類負責增刪改查入口類負責和用戶交互。// 實體類只負責存數(shù)據(jù)不摻業(yè)務邏輯 public class Student { private String id; private String name; private double score; public Student(String id, String name, double score) { this.id id; this.name name; this.score score; } // getter/setter 省略實際代碼里都有 public String toCsv() { return id , name , score; } public static Student fromCsv(String line) { String[] parts line.split(,); return new Student(parts[0], parts[1], Double.parseDouble(parts[2])); } }toCsv()和fromCsv()是一對負責對象和字符串之間的轉換。存文件的時候調toCsv()讀文件的時候調fromCsv()。這種設計叫序列化/反序列化是文件存儲的基礎。注意fromCsv()里用了Double.parseDouble如果文件里的成績不是合法數(shù)字這里會拋NumberFormatException需要在調用處捕獲。管理類負責所有業(yè)務操作import java.io.*; import java.util.*; public class StudentManager { private ListStudent students new ArrayList(); private static final String FILE_PATH students.csv; // 從文件加載數(shù)據(jù)文件不存在時返回空列表 public void loadFromFile() { File file new File(FILE_PATH); if (!file.exists()) { return; } try (BufferedReader reader new BufferedReader(new FileReader(file))) { String line; while ((line reader.readLine()) ! null) { if (!line.trim().isEmpty()) { students.add(Student.fromCsv(line)); } } } catch (IOException e) { System.err.println(讀取文件失敗 e.getMessage()); } } // 保存數(shù)據(jù)到文件 public void saveToFile() { try (BufferedWriter writer new BufferedWriter(new FileWriter(FILE_PATH))) { for (Student s : students) { writer.write(s.toCsv()); writer.newLine(); } } catch (IOException e) { System.err.println(寫入文件失敗 e.getMessage()); } } public void addStudent(Student s) { students.add(s); } public ListStudent getAll() { return Collections.unmodifiableList(students); } }這里有幾個值得說的點。第一try-with-resources語法自動關閉流不用手動寫finally塊代碼更干凈。第二Collections.unmodifiableList返回一個只讀視圖防止外部代碼直接修改內部列表這是封裝思想的體現(xiàn)。第三文件路徑用的是相對路徑students.csv文件會生成在程序運行目錄下。如果你在 IDE 里運行文件可能出現(xiàn)在項目根目錄而不是src里找不到文件的時候先確認運行目錄。4.2 異常處理的分層策略實驗六會涉及異常處理常見的做法是分兩層底層方法拋出異常上層方法捕獲并處理。比如fromCsv解析失敗時拋IllegalArgumentExceptionloadFromFile捕獲后決定是跳過這一行還是終止加載。public static Student fromCsv(String line) { String[] parts line.split(,); if (parts.length ! 3) { throw new IllegalArgumentException(CSV 格式錯誤 line); } try { double score Double.parseDouble(parts[2]); return new Student(parts[0], parts[1], score); } catch (NumberFormatException e) { throw new IllegalArgumentException(成績不是合法數(shù)字 parts[2], e); } }這樣做的好處是異常信息更明確調用方知道具體哪里出了問題。IllegalArgumentException表示參數(shù)不合法NumberFormatException是它的子類這里包了一層并保留了原始異常第二個參數(shù)e方便排查。很多同學寫異常處理就是e.printStackTrace()完事其實把異常信息寫清楚后面調試能省很多時間。4.3 集合選型的實際考量實驗五和實驗六會用到集合選ArrayList還是LinkedList選HashMap還是TreeMap要看具體場景。這份代碼里學生列表用ArrayList因為主要是遍歷和追加隨機訪問多ArrayList的數(shù)組結構更合適。如果要頻繁在中間插入刪除LinkedList更好但實驗場景一般用不上。HashMap用來做學號到學生的映射查找時間復雜度 O(1)。如果需要對學號排序輸出可以換成TreeMap它會自動按 key 排序但插入和查找變成 O(log n)。我一般會先問自己這個數(shù)據(jù)是用來查的還是用來排的查得多就用HashMap排得多就用TreeMap。注意HashMap的 key 如果是自定義對象必須正確重寫hashCode()和equals()否則查找會失效。實驗里一般用String或Integer做 key不會遇到這個問題但二次開發(fā)時要留意。5. 避坑與排查編譯運行中的常見問題5.1 中文亂碼現(xiàn)象程序輸出到控制臺的中文變成問號或方塊。原因Windows 默認編碼是 GBK而 Java 源文件可能是 UTF-8編譯時沒指定編碼。解決編譯時加-encoding UTF-8即javac -encoding UTF-8 *.java。如果用的是 IDEA去Settings → Editor → File Encodings把全局編碼和項目編碼都設成 UTF-8。5.2 找不到主類現(xiàn)象運行java Main時報Could not find or load main class Main。原因要么沒編譯要么類名和文件名不一致要么帶了包名但沒建對應目錄。解決先確認Main.class文件存在再檢查Main.java里public class Main的類名是否和文件名完全一致大小寫敏感。如果類在包里運行時要寫全限定名比如java com.exp1.Main。5.3 文件讀寫路徑錯誤現(xiàn)象實驗六運行時提示FileNotFoundException或文件生成了但找不到。原因相對路徑是相對于“當前工作目錄”不是相對于.java文件所在目錄。在 IDE 里運行工作目錄通常是項目根目錄在命令行運行工作目錄是你執(zhí)行java命令時所在的目錄。解決用絕對路徑測試或者打印System.getProperty(user.dir)看當前工作目錄到底在哪。5.4 空指針異?,F(xiàn)象運行時拋NullPointerException但不知道哪一行。原因調用了null對象的方法或訪問了null對象的字段。解決在可能為null的地方加判斷或者用Optional包裝返回值。調試時看異常堆棧的第一行會指出具體是哪個類的哪一行。實驗五的findById如果返回null而不是Optional調用方忘了判空就會中招。5.5 集合遍歷時刪除元素現(xiàn)象用for-each遍歷ArrayList時刪除元素拋ConcurrentModificationException。原因for-each底層用的是迭代器遍歷過程中修改集合結構會讓迭代器失效。解決用Iterator.remove()方法刪除或者用removeIf()。比如students.removeIf(s - s.getScore() 60)一行就能刪掉所有不及格的比手動遍歷安全。6. 二次開發(fā)與改造思路從實驗代碼到課程設計6.1 把控制臺程序改成圖形界面實驗代碼都是控制臺交互如果你想拿它做課程設計加一個 Swing 或 JavaFX 界面會加分不少。改造思路是保留StudentManager不動新建一個MainFrame類把按鈕事件綁定到管理類的方法上。比如“添加學生”按鈕的監(jiān)聽器里從文本框讀數(shù)據(jù)構造Student對象調manager.addStudent()然后刷新表格。// Swing 按鈕事件綁定示例 addButton.addActionListener(e - { String id idField.getText().trim(); String name nameField.getText().trim(); double score Double.parseDouble(scoreField.getText().trim()); manager.addStudent(new Student(id, name, score)); refreshTable(); // 刷新 JTable 顯示 });這樣改的好處是業(yè)務邏輯和界面分離管理類不用動測試也方便。我一般會先保證控制臺版本跑通再加界面不然出了問題分不清是邏輯錯還是界面錯。6.2 換存儲方式從 CSV 到數(shù)據(jù)庫CSV 文件適合小數(shù)據(jù)量但并發(fā)寫入和查詢效率都不行。二次開發(fā)時可以換成 SQLite不用裝數(shù)據(jù)庫服務器一個文件就是一個庫。用 JDBC 連接把loadFromFile和saveToFile換成 SQL 語句。// 用 JDBC 替換文件存儲 public void saveToDb(Student s) { String sql INSERT INTO student(id, name, score) VALUES(?, ?, ?); try (Connection conn DriverManager.getConnection(jdbc:sqlite:student.db); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, s.getId()); ps.setString(2, s.getName()); ps.setDouble(3, s.getScore()); ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } }用PreparedStatement而不是Statement拼接字符串能防止 SQL 注入也更高效。改造的時候注意異常類型從IOException變成SQLException捕獲邏輯要跟著改。6.3 加單元測試保證改造不出錯改代碼最怕改出 bug。加 JUnit 測試是個好習慣每次改完跑一遍心里有底。比如給StudentManager寫測試import org.junit.Test; import static org.junit.Assert.*; public class StudentManagerTest { Test public void testAddAndFind() { StudentManager manager new StudentManager(); manager.addStudent(new Student(001, 張三, 85.5)); assertEquals(1, manager.getAll().size()); assertEquals(張三, manager.getAll().get(0).getName()); } }測試覆蓋增刪改查和邊界情況空列表、重復學號、非法成績改代碼的時候就有安全網(wǎng)。我一般會先寫測試再改代碼這樣能確保改造不破壞原有功能。6.4 一個容易忽略的細節(jié)equals 和 hashCode如果你在實驗五里用Student對象做HashMap的 key或者用List.contains()判斷學生是否存在必須重寫equals()和hashCode()。默認的實現(xiàn)比較的是對象地址兩個內容相同但地址不同的對象會被認為不相等。Override public boolean equals(Object o) { if (this o) return true; if (o null || getClass() ! o.getClass()) return false; Student student (Student) o; return Objects.equals(id, student.id); } Override public int hashCode() { return Objects.hash(id); }這里用學號作為判斷依據(jù)因為學號是唯一的。Objects.equals和Objects.hash是 Java 7 引入的工具方法能自動處理null比手寫if (id ! null id.equals(...))簡潔。重寫之后list.contains(new Student(001, ...))就能正確返回true了。從那以后我每次改完代碼都會先跑一遍原有的測試用例確認沒破壞已有功能再提交。這個習慣幫我省了不少返工的時間。希望這份實驗代碼和上面的改造思路能幫你把 Java 面向對象真正用起來而不只是應付考試。本文還有配套的精品資源點擊獲取