public class Period {
private final Date startDate;
private final Date endDate;
private Date parseDate(String date) { ... } // 可能回傳 null
public Period(String start, String end) {
startDate = parseDate(start); // 無驗證直接賦值
endDate = parseDate(end);
}
public boolean isValid() {
return startDate != null && endDate != null;
}
public Date getStartDate() {
if (startDate == null) throw new IllegalStateException();
return startDate;
}
// ...
}
缺陷:物件可能處於「無效狀態」 (欄位為 null),呼叫 getter 拋出未檢查異常,違反類型安全,調用者易誤用。
public class Period {
private final Date startDate;
private final Date endDate;
private Date parseDate(String date) throws DateFormatException { ... }
public Period(String start, String end) throws DateFormatException {
startDate = parseDate(start);
endDate = parseDate(end);
}
public Date getStartDate() { return startDate; } // 保證非 null
public Date getEndDate() { return endDate; }
}
調用者被迫處理異常:
try {
Period p = new Period(start, end);
} catch (DateFormatException e) { ... }
優點:編譯器保證物件有效性。缺點:Java 檢查式異常常被濫用/吞噬,導致樣板碼膨脹。
Ceylon 無檢查式異常,採用聯合類型 表達「成功或失敗」:
// 解析函數回傳 Date | DateFormatError
Date|DateFormatError parseDate(String date) => ...;
shared Period|DateFormatError parsePeriod(String start, String end) {
value startDate = parseDate(start);
if (is DateFormatError startDate) return startDate;
value endDate = parseDate(end);
if (is DateFormatError endDate) return endDate;
return Period(startDate, endDate);
}
shared class Period(Date startDate, Date endDate) {
shared Date startDate;
shared Date endDate;
}
調用處:
value p = parsePeriod(start, end);
if (is DateFormatError p) { ... } else { ... }
Date? parseDate(String date) => ...; // 可能為 null
shared Period? parsePeriod(String start, String end) =>
if (exists startDate = parseDate(start), exists endDate = parseDate(end))
then Period(startDate, endDate)
else null;
shared class Period(Date startDate, Date endDate) {
shared Date startDate;
shared Date endDate;
}
| 維度 | Java (檢查式異常) | Ceylon (聯合類型/工廠) |
|---|---|---|
| 表達失敗 | 拋出異常 | 回傳聯合類型 (T|Error 或 T?) |
| 強制處理 | 編譯器強制 try-catch | 編譯器強制類型窄化 |
| 建構子職責 | 驗證+初始化混合 | 純初始化,驗證移至工廠 |
| 物件不變性 | final 欄位保證 | value/共享屬性保證 |
| 程式碼結構 | 分散在建構子/調用處 | 集中在工廠函數 |
Java 與 Ceylon 在物件建構驗證上的差異,折射出兩種語言哲學:Java 依賴異常機制與防禦性檢查,Ceylon 則將失敗納入類型系統,配合工廠函數實現更清晰的職責分離。現代 Java 開發可引入 Optional、Result (Vavr) 或自訂密封類別模擬聯合類型,逐步邁向類型安全的建構模式。