正確判斷閏年關係到日期計算、日曆顯示、財務結算、排程觸發等核心業務準確性。本文將系統講解格里曆閏年規則、兩種經典 Java 實作 (多層 if-else vs 單行邏輯表達式)、完整邊界測試用例、JSR-310 (java.time) 現代 API 用法、常見錯誤與最佳實踐。
數學表達:(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
public class LeapYearClassic { public static boolean isLeapYear(int year) { if (year % 400 == 0) return true; // 規則 1 if (year % 100 == 0) return false; // 規則 2 return year % 4 == 0; // 規則 3/4 } public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.print("請輸入年份: "); int year = sc.nextInt(); System.out.println(year + (isLeapYear(year) ? " 是" : " 不是") + "閏年"); } } public class LeapYearModern { public static boolean isLeapYear(int year) { return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); } // 靜態導入方便測試 public static void main(String[] args) { int[] testCases = {1600, 1700, 1800, 1900, 2000, 2004, 2008, 2012, 2016, 2020, 2024, 1999, 2001, 2002, 2003, 2100, 2200, 2300, 2400}; for (int y : testCases) { System.out.printf("%d: %s%n", y, isLeapYear(y) ? "閏年" : "平年"); } } } | 年份 | 預期 | 規則依據 |
|---|---|---|
| 1600 | 閏年 | %400==0 |
| 1700 | 平年 | %100==0 但 %400!=0 |
| 1800 | 平年 | %100==0 但 %400!=0 |
| 1900 | 平年 | %100==0 但 %400!=0 |
| 2000 | 閏年 | %400==0 (千禧年閏年) |
| 2004 | 閏年 | %4==0 且 %100!=0 |
| 2008 | 閏年 | %4==0 且 %100!=0 |
| 2012 | 閏年 | %4==0 且 %100!=0 |
| 2016 | 閏年 | %4==0 且 %100!=0 |
| 2020 | 閏年 | %4==0 且 %100!=0 |
| 2024 | 閏年 | %4==0 且 %100!=0 |
| 1999 | 平年 | %4!=0 |
| 2001 | 平年 | %4!=0 |
| 2100 | 平年 | %100==0 但 %400!=0 (未來陷阱) |
| 2200 | 平年 | %100==0 但 %400!=0 |
| 2300 | 平年 | %100==0 但 %400!=0 |
| 2400 | 閏年 | %400==0 |
不要手寫算法! 直接使用標準庫:
import java.time.Year; public class LeapYearJSR310 { public static boolean isLeapYear(int year) { return Year.of(year).isLeap(); } public static void main(String[] args) { // 測試 System.out.println(Year.of(2000).isLeap()); // true System.out.println(Year.of(1900).isLeap()); // false System.out.println(Year.of(2100).isLeap()); // false // 額外功能:獲取該年天數、月份天數 System.out.println(Year.of(2024).length()); // 366 System.out.println(YearMonth.of(2024, 2).lengthOfMonth()); // 29 } } Year.of(year).isLeap() 一目了然length()、YearMonth.lengthOfMonth()、LocalDate.daysInMonth()| 錯誤寫法 | 問題 | 修正 |
|---|---|---|
year % 4 == 0 | 漏掉 100/400 規則,1900/2100 會誤判為閏年 | 加上完整邏輯 |
(year % 4 == 0 || year % 400 == 0) && year % 100 != 0 | 邏輯錯誤:2000 會被 %100!=0 排除 | 正確:(year%4==0 && year%100!=0) || year%400==0 |
使用 GregorianCalendar | 舊 API 設計差、可變、已棄用 | 改用 java.time.Year |
| 年份輸入未驗證 | 負數、超大數值導致邏輯異常 | 限定範圍如 1583-9999 (格里曆生效起) |
// JMH 基準測試粗略結果 (ops/ms, 越高越好) // 手寫單行表達式: ~15,000,000 // Year.of(year).isLeap(): ~12,000,000 // 差異微乎其微,現代 JVM 內聯優化極強 // 結論:直接用 Year.isLeap(),可讀性、正確性、維護性完勝 閏年判斷核心邏輯:(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)。
生產代碼請直接使用 Year.of(year).isLeap() (JDK 8+),拒絕手寫算法、拒絕舊 Calendar API。若必須手寫 (面試、教學、受限環境),務必包含完整邊界測試 (特別是 1900、2000、2100 三大關鍵點)。日期處理無小事,標準庫是你最可靠的夥伴。