當前位置: 妍妍網 > 碼農

公司空降一個 CTO:禁止在計畫中使用 Date 類!!!

2024-05-16碼農

大家好,我是鵬磊。

一、有什麽問題嗎java.util.Date?

java.util.Date (從現在開始)是一個糟糕的型別,這解釋了為什麽它的大部份內容在 Java 1.1 中被棄用(但不幸的是仍在使用)。

設計缺陷包括:

  • 它的名稱具有誤導性:它並不代表一個日期,而是代表時間的一個瞬間。所以它應該被稱為Instant——正如它的java.time等價物一樣。

  • 它是非最終的:這鼓勵了對繼承的不良使用,例如java.sql.Date(這 意味著 代表一個日期,並且由於具有相同的短名稱而也令人困惑)

  • 它是可變的:日期/時間型別是自然值,可以透過不可變型別有效地建模。可變的事實Date(例如透過setTime方法)意味著勤奮的開發人員最終會在各處建立防禦性副本。

  • 它在許多地方(包括)隱式使用系統本地時區,toString()這讓許多開發人員感到困惑。有關此內容的更多資訊,請參閱「什麽是即時」部份

  • 它的月份編號是從 0 開始的,是從 C 語言復制的。這導致了很多很多相差一的錯誤。

  • 它的年份編號是基於 1900 年的,也是從 C 語言復制的。當然,當 Java 出現時,我們已經意識到這不利於可讀性?

  • 它的方法命名不明確:getDate()返回月份中的某一天,並getDay()返回星期幾。給這些更具描述性的名字有多難?

  • 如果你近期準備面試跳槽,建議在ddkk.com線上刷題,涵蓋 一萬+ 道 Java 面試題,幾乎覆蓋了所有主流技術面試題,還有市面上最全的技術五百套,精品系列教程,免費提供。

  • 對於是否支持閏秒含糊其辭:「秒由 0 到 61 之間的整數表示;值 60 和 61 僅在閏秒時出現,即使如此,也僅在實際正確跟蹤閏秒的 Java 實作中出現。」 我強烈懷疑大多數開發人員(包括我自己)都做了很多假設,認為 for 的範圍getSeconds()實際上在 0-59 範圍內(含)。

  • 它的寬容沒有明顯的理由:「在所有情況下,為這些目的而對方法給出的論據不必落在指定的範圍內; 例如,日期可以指定為 1 月 32 日,並被解釋為 2 月 1 日。」 多久有用一次?

  • 關鍵原因如下:

    原文如下:為什麽要避免使用Date類?

    二、為啥要改?

    我們要改的原因很簡單,我們的程式碼缺陷掃描規則認為這是一個必須修改的缺陷,否則不給釋出,不改不行,服了。

    解決思路:避免使用 java.util.Date java.sql.Date 類和其提供的API,考慮使用 java.time.Instant 類或 java.time.LocalDateTime 類及其提供的API替代。

    三、怎麽改?

    只能說這種基礎的類改起來牽一發動全身,需要從DO實體類看起,然後就是各種Converter,最後是DTO。由於我們還是微服務架構,業務服務依賴於基礎服務的API,所以必須要一起改否則就會報錯。這裏就不細說修改流程了,主要說一下我們在改造的時候遇到的一些問題。

    如果你近期準備面試跳槽,建議在ddkk.com線上刷題,涵蓋 一萬+ 道 Java 面試題,幾乎覆蓋了所有主流技術面試題,還有市面上最全的技術五百套,精品系列教程,免費提供。

    1. 耐心比對資料庫日期欄位和DO的對映

    1)確定欄位型別

    首先你需要確定數據物件中的 Date 欄位代表的是日期、時間還是時間戳。

  • 如果欄位代表日期和時間,則可能需要使用 LocalDateTime。

  • 如果欄位僅代表日期,則可能需要使用 LocalDate。

  • 如果欄位僅代表時間,則可能需要使用 LocalTime。

  • 如果欄位需要保存時間戳(帶時區的),則可能需要使用 Instant 或 ZonedDateTime。

  • 2)更新數據物件類

    更新數據物件類中的欄位,把 Date 型別改為適當的 java.time 型別。

    2. 將DateUtil中的方法改造

    1)替換原來的new Date()和Calendar.getInstance().getTime()

    原來的方式:

    Date nowDate = new Date();
    Date nowCalendarDate = Calendar.getInstance().getTime();

    使用 java.time 改造後:

    // 使用Instant代表一個時間點,這與Date類似
    Instant nowInstant = Instant.now();
    // 如果需要用到具體的日期和時間(例如年、月、日、時、分、秒)
    LocalDateTime nowLocalDateTime = LocalDateTime.now();
    // 如果你需要和特定的時區互動,可以使用ZonedDateTime
    ZonedDateTime nowZonedDateTime = ZonedDateTime.now();
    // 如果你需要轉換回java.util.Date,你可以這樣做(假設你的程式碼其他部份還需要使用Date)
    Date nowFromDateInstant = Date.from(nowInstant);
    // 如果需要與java.sql.Timestamp互動
    java.sql.Timestamp nowFromInstant = java.sql.Timestamp.from(nowInstant);


    一些註意點:

    1、 Instant 表示的是一個時間點,它是時區無關的,相當於舊的 Date 類它通常用於表示時間戳;
    2、 LocalDateTime 表示沒有時區資訊的日期和時間,它不能直接轉換為時間戳,除非你將其與時區結合使用(例如透過 ZonedDateTime );
    3、 ZonedDateTime 包含時區資訊的日期和時間,它更類似於 Calendar ,因為 Calendar 也包含時區資訊;
    4、 當你需要將 java.time 物件轉換回 java.util.Date 物件時,可以使用 Date.from(Instant) 方法這在你的程式碼需要與舊的API或庫互動時非常有用;

    2)一些基礎的方法改造

    a. dateFormat

    原來的方式

    publicstatic String dateFormat(Date date, String dateFormat){
    SimpleDateFormat formatter = new SimpleDateFormat(dateFormat);
    return formatter.format(date);
    }

    使用 java.time 改造後

    publicstatic String dateFormat(LocalDateTime date, String dateFormat){
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
    return date.format(formatter);
    }

    b. addSecond、addMinute、addHour、addDay、addMonth、addYear

    原來的方式

    publicstatic Date addSecond(Date date, int second){
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(13, second);
    return calendar.getTime();
    }
    publicstatic Date addMinute(Date date, int minute){
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(12, minute);
    return calendar.getTime();
    }
    publicstatic Date addHour(Date date, int hour){
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(10, hour);
    return calendar.getTime();
    }
    publicstatic Date addDay(Date date, int day){
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(5, day);
    return calendar.getTime();
    }
    publicstatic Date addMonth(Date date, int month){
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(2, month);
    return calendar.getTime();
    }
    publicstatic Date addYear(Date date, int year){
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(date);
    calendar.add(1, year);
    return calendar.getTime();
    }



    使用 java.time 改造後

    publicstatic LocalDateTime addSecond(LocalDateTime date, int second){
    return date.plusSeconds(second);
    }
    publicstatic LocalDateTime addMinute(LocalDateTime date, int minute){
    return date.plusMinutes(minute);
    }
    publicstatic LocalDateTime addHour(LocalDateTime date, int hour){
    return date.plusHours(hour);
    }
    publicstatic LocalDateTime addDay(LocalDateTime date, int day){
    return date.plusDays(day);
    }
    publicstatic LocalDateTime addMonth(LocalDateTime date, int month){
    return date.plusMonths(month);
    }
    publicstatic LocalDateTime addYear(LocalDateTime date, int year){
    return date.plusYears(year);
    }



    c. dateToWeek

    原來的方式

    publicstaticfinal String[] WEEK_DAY_OF_CHINESE = new String[]{"周日""周一""周二""周三""周四""周五""周六"};
    publicstatic String dateToWeek(Date date){
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    return WEEK_DAY_OF_CHINESE[cal.get(7) - 1];
    }

    使用 java.time 改造後

    publicstaticfinal String[] WEEK_DAY_OF_CHINESE = new String[]{"周日""周一""周二""周三""周四""周五""周六"};
    publicstatic String dateToWeek(LocalDate date){
    DayOfWeek dayOfWeek = date.getDayOfWeek();
    return WEEK_DAY_OF_CHINESE[dayOfWeek.getValue() % 7];
    }

    d. getStartOfDay和getEndOfDay

    原來的方式

    publicstatic Date getStartTimeOfDay(Date date){
    if (date == null) {
    returnnull;
    else {
    LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());
    LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);
    return Date.from(startOfDay.atZone(ZoneId.systemDefault()).toInstant());
    }
    }
    publicstatic Date getEndTimeOfDay(Date date){
    if (date == null) {
    returnnull;
    else {
    LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());
    LocalDateTime endOfDay = localDateTime.with(LocalTime.MAX);
    return Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant());
    }
    }

    使用 java.time 改造後

    publicstatic LocalDateTime getStartTimeOfDay(LocalDateTime date){
    if (date == null) {
    returnnull;
    else {
    // 獲取一天的開始時間,即00:00
    return date.toLocalDate().atStartOfDay();
    }
    }
    publicstatic LocalDateTime getEndTimeOfDay(LocalDateTime date){
    if (date == null) {
    returnnull;
    else {
    // 獲取一天的結束時間,即23:59:59.999999999
    return date.toLocalDate().atTime(LocalTime.MAX);
    }
    }

    e. betweenStartAndEnd

    原來的方式

    publicstatic Boolean betweenStartAndEnd(Date nowTime, Date beginTime, Date endTime){
    Calendar date = Calendar.getInstance();
    date.setTime(nowTime);
    Calendar begin = Calendar.getInstance();
    begin.setTime(beginTime);
    Calendar end = Calendar.getInstance();
    end.setTime(endTime);
    return date.after(begin) && date.before(end);
    }

    使用 java.time 改造後

    publicstatic Boolean betweenStartAndEnd(Instant nowTime, Instant beginTime, Instant endTime){
    return nowTime.isAfter(beginTime) && nowTime.isBefore(endTime);
    }

    我這裏就只列了一些,如果有缺失的可以自己補充,不會寫的話直接問問ChatGPT,它最會幹這事了。最後把這些修改後的方法替換一下就行了。

    四、小結一下

    這個改造難度不高,但是復雜度非常高,一個地方沒改好,輕則介面報錯,重則啟動失敗,非常耗費精力,真不想改。

    🔥 磊哥私藏精品 熱門推薦 🔥