專案中常需將不同實體類別的集合匯出為 CSV 檔案。若為每個類別寫死匯出邏輯,程式碼冗餘且難維護。利用 Java 泛型與反射,可實作一個通用方法,自動根據物件屬性動態生成標題列與資料列,大幅簡化開發工作量。
import java.io.*;
import java.lang.reflect.Field;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
public class CsvExporter {
/**
* 將任意物件列表匯出為 CSV
* @param list 資料列表,不可為 null 或空
* @param outFile 輸出檔案完整路徑
* @throws IOException IO 異常
* @throws IllegalArgumentException 路徑為空
*/
public static void exportToCsv(List list, String outFile) throws IOException {
if (list == null || list.isEmpty()) {
return; // 無資料不生成檔案
}
if (StringUtils.isEmpty(outFile)) {
throw new IllegalArgumentException("輸出檔案路徑不可為空");
}
boolean isFirst = true;
try (BufferedWriter writer = new BufferedWriter(new FileWriter(outFile))) {
for (T obj : list) {
StringBuilder headerSb = new StringBuilder();
StringBuilder dataSb = new StringBuilder();
Class> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true); // 允許存取 private 欄位
try {
if (isFirst) {
headerSb.append(field.getName()).append(',');
}
Object value = field.get(obj);
dataSb.append(value != null ? value.toString() : "").append(',');
} catch (IllegalAccessException e) {
// 理論上不會發生,因已 setAccessible(true)
e.printStackTrace();
}
}
if (isFirst) {
writer.write(headerSb.toString());
writer.newLine();
isFirst = false;
}
writer.write(dataSb.toString());
writer.newLine();
}
}
}
}
<T>:方法接受任意類型列表,編譯期型別安全,呼叫端無需強制轉型。Field[] getDeclaredFields():取得類別宣告的所有欄位(含 private),不包含繼承自父類的欄位。若需包含父類欄位,可遞迴遍歷 getSuperclass()。field.setAccessible(true):繞過 Java 存取修飾符檢查,直接讀取私有欄位值。try-with-resources 自動關閉 BufferedWriter,確保檔案正確落盤。null 時輸出空字串,避免 NullPointerException。// 實體類別
public class User {
private Long id;
private String username;
private String email;
private Integer age;
// getter/setter 省略
}
// 呼叫匯出
List users = userService.findAll();
CsvExporter.exportToCsv(users, "D:/export/users.csv");
@CsvColumn(order = 1, name = "使用者ID"),透過反射讀取註解控制輸出順序與中文標題。Date、BigDecimal 等類型套用 SimpleDateFormat 或 DecimalFormat。ScrollableResultSet 或分頁查詢,避免一次載入所有資料導致 OOM。FileWriter 時指定 StandardCharsets.UTF_8(或加 BOM 的 UTF-8)相容 Excel 開啟。| 問題 | 原因 | 解決方案 |
|---|---|---|
| CSV 開啟中文亂碼 | Excel 預設用系統編碼(如 Big5)讀取 UTF-8 檔 | 輸出 UTF-8 with BOM,或使用 Excel「資料→從文字/CSV」匯入指定編碼 |
| 欄位包含逗號/換行導致欄位錯位 | 未處理 CSV 特殊字元 | 值包含逗號、雙引號、換行時,以雙引號包裹並將內部雙引號轉義為兩個雙引號 |
| 父類別欄位未匯出 | getDeclaredFields() 僅取得當前類別欄位 | 遞迴遍歷 clazz.getSuperclass() 直到 Object.class |
| 靜態欄位被匯出 | 未過濾 Modifier.isStatic(field.getModifiers()) | 在迴圈中判斷並跳過 static/final/transient 欄位 |
以泛型與反射實作通用 CSV 匯出,是 Java 後端開發中極具性價比的工具化實踐。配合註解擴充與異常處理,即可滿足企業級報表匯出的大多數需求,建議封裝為專案共用工具模組持續演進。