首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java 日期與字串互轉工具類:多格式自動解析、執行緒安全與 Java 8 時間 API 現代化升級指南

     編輯:Java 綜合教學

傳統方案回顧:SimpleDateFormat 多格式兼容解析

原始碼提供了一個實用的工具類,嘗試按順序嘗試多種日期格式解析字串,解析成功即返回,失敗則嘗試下一個格式。這在對接多系統、用戶輸入格式不固定的場景很常見。

原始碼重構要點

package cn.zwq.convert;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateConvert {

    // 1. 靜態常量:預定義支援的輸入格式(按優先級排序)
    private static final String[] INPUT_PATTERNS = {
        "yyyy-MM-dd",    // 標準 ISO 日期
        "yyyyMMdd",      // 緊湊格式
        "yyyy.MM.dd",    // 點分隔
        "yyyy/MM/dd",    // 斜線分隔
        "yyyy年MM月dd日"  // 中文格式
    };

    // 2. 輸出格式統一為中文長格式
    private static final String OUTPUT_PATTERN = "yyyy年MM月dd日";

    // 3. 核心解析方法:順序嘗試,成功即返回
    public static Date stringToDate(String source) {
        if (source == null || source.trim().isEmpty()) {
            return null;
        }
        String trimmed = source.trim();
        for (String pattern : INPUT_PATTERNS) {
            try {
                // 注意:SimpleDateFormat 非執行緒安全,此處每次 new 實例
                return new SimpleDateFormat(pattern).parse(trimmed);
            } catch (ParseException ignored) {
                // 忽略,嘗試下一個格式
            }
        }
        return null; // 所有格式均失敗
    }

    // 4. 格式化輸出
    public static String dateToString(Date date) {
        if (date == null) return null;
        return new SimpleDateFormat(OUTPUT_PATTERN).format(date);
    }

    // 測試主方法
    public static void main(String[] args) {
        String[] tests = {"1979-01-01", "19790101", "1979.01.01", "1979/01/01", "1979年01月01日"};
        for (String s : tests) {
            Date d = stringToDate(s);
            System.out.println(s + " -> " + dateToString(d));
        }
    }
}

關鍵問題:SimpleDateFormat 非執行緒安全

  • 類別級別共享 SimpleDateFormat 實例會導致併發解析/格式化錯誤(數據錯亂、拋異常)。
  • 上述寫法每次 new SimpleDateFormat() 雖安全但效能較差(頻繁創建對象、編譯模式)。

現代化升級:Java 8+ java.time API 完整替代方案

強烈建議專案遷移至 java.time(JSR-310):不可變、執行緒安全、API 豐富、效能更優。

1. 核心類別對照

舊 API (java.util/java.sql)新 API (java.time)說明
DateInstant / LocalDateTime / ZonedDateTime時間線上的瞬間 / 無時區日期時間 / 有時區日期時間
CalendarLocalDate / LocalTime / LocalDateTime日期 / 時間 / 日期時間
SimpleDateFormatDateTimeFormatter執行緒安全、不可變、可預編譯常量
TimeZoneZoneId / ZoneOffset時區規則 / 固定偏移量

2. 多格式解析器(執行緒安全、高效能)

package cn.zwq.convert;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.Locale;

public class ModernDateConvert {

    // 1. 預編譯 Formatter 常量(執行緒安全、零開銷共享)
    private static final DateTimeFormatter[] INPUT_FORMATTERS = Arrays.stream(new String[]{
        "yyyy-MM-dd",
        "yyyyMMdd",
        "yyyy.MM.dd",
        "yyyy/MM/dd",
        "yyyy年MM月dd日"
    }).map(pattern -> DateTimeFormatter.ofPattern(pattern, Locale.CHINA))
      .toArray(DateTimeFormatter[]::new);

    private static final DateTimeFormatter OUTPUT_FORMATTER = 
        DateTimeFormatter.ofPattern("yyyy年MM月dd日", Locale.CHINA);

    // 2. 解析為 LocalDate(無時區、不可變、推薦用於業務邏輯)
    public static LocalDate parseToLocalDate(String source) {
        if (source == null || source.trim().isEmpty()) return null;
        String trimmed = source.trim();
        for (DateTimeFormatter fmt : INPUT_FORMATTERS) {
            try {
                return LocalDate.parse(trimmed, fmt);
            } catch (DateTimeParseException ignored) {}
        }
        return null;
    }

    // 3. 兼容舊 API:轉換為 java.util.Date(如需對接舊庫)
    public static java.util.Date parseToDate(String source) {
        LocalDate ld = parseToLocalDate(source);
        return ld == null ? null : java.util.Date.from(ld.atStartOfDay(java.time.ZoneId.systemDefault()).toInstant());
    }

    // 4. 格式化輸出
    public static String format(LocalDate date) {
        return date == null ? null : OUTPUT_FORMATTER.format(date);
    }

    public static String format(java.util.Date date) {
        if (date == null) return null;
        return OUTPUT_FORMATTER.format(date.toInstant()
            .atZone(java.time.ZoneId.systemDefault())
            .toLocalDate());
    }

    // 測試
    public static void main(String[] args) {
        String[] tests = {"1979-01-01", "19790101", "1979.01.01", "1979/01/01", "1979年01月01日", "invalid"};
        for (String s : tests) {
            LocalDate ld = parseToLocalDate(s);
            System.out.printf("%-20s -> %s%n", s, ld == null ? "解析失敗" : format(ld));
        }
    }
}

3. 進階:單一 Formatter 支援多種模式(DateTimeFormatterBuilder)

// 使用可選區段 [] 合併多種分隔符模式
DateTimeFormatter flexible = new DateTimeFormatterBuilder()
    .appendPattern("yyyy")
    .optionalStart().appendPattern("[-[.][/][年]]").optionalEnd() // 年後分隔符可選
    .appendPattern("MM")
    .optionalStart().appendPattern("[-[.][/][月]]").optionalEnd() // 月後分隔符可選
    .appendPattern("dd")
    .optionalStart().appendPattern("[日]").optionalEnd()          // 可選「日」
    .toFormatter(Locale.CHINA);

// 可一次解析 "2023-10-15", "2023.10.15", "2023/10/15", "2023年10月15日", "20231015"
LocalDate date = LocalDate.parse("2023年10月15日", flexible); // 成功

三、遷移檢查清單與最佳實踐

  1. 新代碼全面使用 java.time,停止引入 SimpleDateFormat、Calendar、Date(除非對接強制要求的舊 API)。
  2. Formatter 定義為 static final 常量,全局共享,零同步開銷。
  3. 業務層統一用 LocalDate/LocalDateTime,邊界層(Controller、DAO、序列化)做轉換。
  4. 資料庫層:MyBatis/JPA 直接映射 LocalDate/LocalDateTime(需啟用對應 TypeHandler/Converter)。
  5. JSON 序列化:Jackson 2.10+ 原生支援 java.time,無需額外模組;舊版本加 jackson-datatype-jsr310 並註冊 JavaTimeModule。
  6. 時區處理:明確指定 ZoneId,避免依賴 JVM 預設時區導致跨環境不一致。

四、總結

原始工具類邏輯清晰、實用性強,但受限於 SimpleDateFormat 非執行緒安全與效能短板。遷移至 Java 8 java.time API 可一舉解決安全、效能、API 易用性三大痛點,並獲得不可變時間物件帶來的領域建模優勢。本文提供的 ModernDateConvert 可直接作為專案通用工具類投入使用,並展示了 DateTimeFormatterBuilder 合併模式的進階技巧,滿足更複雜的兼容性需求。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved