強(qiáng)烈建議你不要再使用Date類了?。。?/h1>
往期熱門文章:
2、Java內(nèi)部類有坑,100%內(nèi)存泄露!
4、被問懵了,加密后的數(shù)據(jù)如何進(jìn)行模糊查詢?
5、中國(guó)程序員獨(dú)立開發(fā)9年、最受歡迎的開源Redis客戶端,被Redis公司收購(gòu)了!
一、有什么問題嗎java.util.Date?
java.util.Date(Date從現(xiàn)在開始)是一個(gè)糟糕的類型,這解釋了為什么它的大部分內(nèi)容在 Java 1.1 中被棄用(但不幸的是仍在使用)。
設(shè)計(jì)缺陷包括:
-
它的名稱具有誤導(dǎo)性: 它并不代表一個(gè)日期,而是代表時(shí)間的一個(gè)瞬間。所以它應(yīng)該被稱為Instant——正如它的java.time等價(jià)物一樣。
-
它是非最終的: 這鼓勵(lì)了對(duì)繼承的不良使用,例如java.sql.Date(這意味著代表一個(gè)日期,并且由于具有相同的短名稱而也令人困惑)
-
它是可變的: 日期/時(shí)間類型是自然值,可以通過不可變類型有效地建模??勺兊氖聦?shí)Date(例如通過setTime方法)意味著勤奮的開發(fā)人員最終會(huì)在各處創(chuàng)建防御性副本。
-
它在許多地方(包括)隱式使用系統(tǒng)本地時(shí)區(qū),toString()這讓許多開發(fā)人員感到困惑。有關(guān)此內(nèi)容的更多信息,請(qǐng)參閱“什么是即時(shí)”部分
-
它的月份編號(hào)是從 0 開始的,是從 C 語(yǔ)言復(fù)制的。這導(dǎo)致了很多很多相差一的錯(cuò)誤。
-
它的年份編號(hào)是基于 1900 年的,也是從 C 語(yǔ)言復(fù)制的。當(dāng)然,當(dāng) Java 出現(xiàn)時(shí),我們已經(jīng)意識(shí)到這不利于可讀性?
-
它的方法命名不明確: getDate()返回月份中的某一天,并getDay()返回星期幾。給這些更具描述性的名字有多難?
-
對(duì)于是否支持閏秒含糊其辭: “秒由 0 到 61 之間的整數(shù)表示;值 60 和 61 僅在閏秒時(shí)出現(xiàn),即使如此,也僅在實(shí)際正確跟蹤閏秒的 Java 實(shí)現(xiàn)中出現(xiàn)?!?我強(qiáng)烈懷疑大多數(shù)開發(fā)人員(包括我自己)都做了很多假設(shè),認(rèn)為 for 的范圍getSeconds()實(shí)際上在 0-59 范圍內(nèi)(含)。
-
它的寬容沒有明顯的理由: “在所有情況下,為這些目的而對(duì)方法給出的論據(jù)不必落在指定的范圍內(nèi); 例如,日期可以指定為 1 月 32 日,并被解釋為 2 月 1 日。” 多久有用一次?
關(guān)鍵原因如下:

原文如下:為什么要避免使用Date類?
https://codeblog.jonskeet.uk/2017/04/23/all-about-java-util-date/
二、為啥要改?
我們要改的原因很簡(jiǎn)單,我們的代碼缺陷掃描規(guī)則認(rèn)為這是一個(gè)必須修改的缺陷,否則不給發(fā)布,不改不行,服了。
解決思路:避免使用java.util.Date與java.sql.Date類和其提供的API,考慮使用java.time.Instant類或java.time.LocalDateTime類及其提供的API替代。
三、怎么改?
只能說這種基礎(chǔ)的類改起來牽一發(fā)動(dòng)全身,需要從DO實(shí)體類看起,然后就是各種Converter,最后是DTO。
由于我們還是微服務(wù)架構(gòu),業(yè)務(wù)服務(wù)依賴于基礎(chǔ)服務(wù)的API,所以必須要一起改否則就會(huì)報(bào)錯(cuò)。這里就不細(xì)說修改流程了,主要說一下我們?cè)诟脑斓臅r(shí)候遇到的一些問題。
1. 耐心比對(duì)數(shù)據(jù)庫(kù)日期字段和DO的映射
1)確定字段類型
首先你需要確定數(shù)據(jù)對(duì)象中的 Date 字段代表的是日期、時(shí)間還是時(shí)間戳。
-
如果字段代表日期和時(shí)間,則可能需要使用 LocalDateTime。
-
如果字段僅代表日期,則可能需要使用 LocalDate。
-
如果字段僅代表時(shí)間,則可能需要使用 LocalTime。
-
如果字段需要保存時(shí)間戳(帶時(shí)區(qū)的),則可能需要使用 Instant 或 ZonedDateTime。
2)更新數(shù)據(jù)對(duì)象類
更新數(shù)據(jù)對(duì)象類中的字段,把 Date 類型改為適當(dāng)?shù)?java.time 類型。
2. 將DateUtil中的方法改造
1)替換原來的new Date()和Calendar.getInstance().getTime()
原來的方式:
Date nowDate = new Date();
Date nowCalendarDate = Calendar.getInstance().getTime();
使用 java.time 改造后:
// 使用Instant代表一個(gè)時(shí)間點(diǎn),這與Date類似
Instant nowInstant = Instant.now();
// 如果需要用到具體的日期和時(shí)間(例如年、月、日、時(shí)、分、秒)
LocalDateTime nowLocalDateTime = LocalDateTime.now();
// 如果你需要和特定的時(shí)區(qū)交互,可以使用ZonedDateTime
ZonedDateTime nowZonedDateTime = ZonedDateTime.now();
// 如果你需要轉(zhuǎn)換回java.util.Date,你可以這樣做(假設(shè)你的代碼其他部分還需要使用Date)
Date nowFromDateInstant = Date.from(nowInstant);
// 如果需要與java.sql.Timestamp交互
java.sql.Timestamp nowFromInstant = java.sql.Timestamp.from(nowInstant);
一些注意點(diǎn):
-
Instant 表示的是一個(gè)時(shí)間點(diǎn),它是時(shí)區(qū)無關(guān)的,相當(dāng)于舊的 Date 類。它通常用于表示時(shí)間戳。
-
LocalDateTime 表示沒有時(shí)區(qū)信息的日期和時(shí)間,它不能直接轉(zhuǎn)換為時(shí)間戳,除非你將其與時(shí)區(qū)結(jié)合使用(例如通過 ZonedDateTime)。
-
ZonedDateTime 包含時(shí)區(qū)信息的日期和時(shí)間,它更類似于 Calendar,因?yàn)?Calendar 也包含時(shí)區(qū)信息。
-
當(dāng)你需要將 java.time 對(duì)象轉(zhuǎn)換回 java.util.Date 對(duì)象時(shí),可以使用 Date.from(Instant) 方法。這在你的代碼需要與舊的API或庫(kù)交互時(shí)非常有用。
2)一些基礎(chǔ)的方法改造
a. dateFormat
原來的方式
public static String dateFormat(Date date, String dateFormat) {
SimpleDateFormat formatter = new SimpleDateFormat(dateFormat);
return formatter.format(date);
}
使用java.time改造后
public static String dateFormat(LocalDateTime date, String dateFormat) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
return date.format(formatter);
}
b. addSecond、addMinute、addHour、addDay、addMonth、addYear
原來的方式
public static Date addSecond(Date date, int second) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(13, second);
return calendar.getTime();
}
public static Date addMinute(Date date, int minute) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(12, minute);
return calendar.getTime();
}
public static Date addHour(Date date, int hour) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(10, hour);
return calendar.getTime();
}
public static Date addDay(Date date, int day) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(5, day);
return calendar.getTime();
}
public static Date addMonth(Date date, int month) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(2, month);
return calendar.getTime();
}
public static Date addYear(Date date, int year) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(1, year);
return calendar.getTime();
}
使用java.time改造后
public static LocalDateTime addSecond(LocalDateTime date, int second) {
return date.plusSeconds(second);
}
public static LocalDateTime addMinute(LocalDateTime date, int minute) {
return date.plusMinutes(minute);
}
public static LocalDateTime addHour(LocalDateTime date, int hour) {
return date.plusHours(hour);
}
public static LocalDateTime addDay(LocalDateTime date, int day) {
return date.plusDays(day);
}
public static LocalDateTime addMonth(LocalDateTime date, int month) {
return date.plusMonths(month);
}
public static LocalDateTime addYear(LocalDateTime date, int year) {
return date.plusYears(year);
}
c. dateToWeek
原來的方式
public static final String[] WEEK_DAY_OF_CHINESE = new String[]{"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
public static String dateToWeek(Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
return WEEK_DAY_OF_CHINESE[cal.get(7) - 1];
}
使用java.time改造后
public static final String[] WEEK_DAY_OF_CHINESE = new String[]{"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
public static String dateToWeek(LocalDate date) {
DayOfWeek dayOfWeek = date.getDayOfWeek();
return WEEK_DAY_OF_CHINESE[dayOfWeek.getValue() % 7];
}
d. getStartOfDay和getEndOfDay
原來的方式
public static Date getStartTimeOfDay(Date date) {
if (date == null) {
return null;
} 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());
}
}
public static Date getEndTimeOfDay(Date date) {
if (date == null) {
return null;
} 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改造后
public static LocalDateTime getStartTimeOfDay(LocalDateTime date) {
if (date == null) {
return null;
} else {
// 獲取一天的開始時(shí)間,即00:00
return date.toLocalDate().atStartOfDay();
}
}
public static LocalDateTime getEndTimeOfDay(LocalDateTime date) {
if (date == null) {
return null;
} else {
// 獲取一天的結(jié)束時(shí)間,即23:59:59.999999999
return date.toLocalDate().atTime(LocalTime.MAX);
}
}
e. betweenStartAndEnd
原來的方式
public static 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改造后
public static Boolean betweenStartAndEnd(Instant nowTime, Instant beginTime, Instant endTime) {
return nowTime.isAfter(beginTime) && nowTime.isBefore(endTime);
}
我這里就只列了一些,如果有缺失的可以自己補(bǔ)充,不會(huì)寫的話直接問問ChatGPT,它最會(huì)干這事了。最后把這些修改后的方法替換一下就行了。
四、小結(jié)一下
這個(gè)改造難度不高,但是復(fù)雜度非常高,一個(gè)地方?jīng)]改好,輕則接口報(bào)錯(cuò),重則啟動(dòng)失敗,非常耗費(fèi)精力,真不想改。
來源|c(diǎn)nblogs.com/wlovet/p/18058514
往期熱門文章:
1、我在代碼里面故意留個(gè)漏洞,違法嗎?
2、一個(gè)現(xiàn)代化輕量級(jí)的跨平臺(tái) Redis 桌面客戶端,支持 Mac、Windows 和 Linux
3、能用到“退休”的 600條 Linux 命令,可以解決日常99%的問題~
4、工作六年,看到這樣的代碼,內(nèi)心五味雜陳...
5、拒絕寫重復(fù)代碼,試試這套開源的 SpringBoot 組件
6、一次生產(chǎn)事故,來來回回搞了一個(gè)月,人麻了!
7、面試官:Git 如何撤回已 Push 的代碼?問倒一大片。。。
8、SpringBoot 如何快速過濾出一次請(qǐng)求的所有日志?
9、千萬不要把 Request 傳遞到異步線程里面!有坑!
10、別再用 offset 和 limit 分頁(yè)了,性能太差!
瀏覽
43
往期熱門文章:
2、Java內(nèi)部類有坑,100%內(nèi)存泄露!
4、被問懵了,加密后的數(shù)據(jù)如何進(jìn)行模糊查詢?
5、中國(guó)程序員獨(dú)立開發(fā)9年、最受歡迎的開源Redis客戶端,被Redis公司收購(gòu)了!
java.util.Date(Date從現(xiàn)在開始)是一個(gè)糟糕的類型,這解釋了為什么它的大部分內(nèi)容在 Java 1.1 中被棄用(但不幸的是仍在使用)。
-
它的名稱具有誤導(dǎo)性: 它并不代表一個(gè)日期,而是代表時(shí)間的一個(gè)瞬間。所以它應(yīng)該被稱為Instant——正如它的 java.time等價(jià)物一樣。 -
它是非最終的: 這鼓勵(lì)了對(duì)繼承的不良使用,例如 java.sql.Date(這意味著代表一個(gè)日期,并且由于具有相同的短名稱而也令人困惑) -
它是可變的: 日期/時(shí)間類型是自然值,可以通過不可變類型有效地建模??勺兊氖聦?shí)Date(例如通過setTime方法)意味著勤奮的開發(fā)人員最終會(huì)在各處創(chuàng)建防御性副本。 -
它在許多地方(包括)隱式使用系統(tǒng)本地時(shí)區(qū), toString()這讓許多開發(fā)人員感到困惑。有關(guān)此內(nèi)容的更多信息,請(qǐng)參閱“什么是即時(shí)”部分 -
它的月份編號(hào)是從 0 開始的,是從 C 語(yǔ)言復(fù)制的。這導(dǎo)致了很多很多相差一的錯(cuò)誤。 -
它的年份編號(hào)是基于 1900 年的,也是從 C 語(yǔ)言復(fù)制的。當(dāng)然,當(dāng) Java 出現(xiàn)時(shí),我們已經(jīng)意識(shí)到這不利于可讀性? -
它的方法命名不明確: getDate()返回月份中的某一天,并getDay()返回星期幾。給這些更具描述性的名字有多難? -
對(duì)于是否支持閏秒含糊其辭: “秒由 0 到 61 之間的整數(shù)表示;值 60 和 61 僅在閏秒時(shí)出現(xiàn),即使如此,也僅在實(shí)際正確跟蹤閏秒的 Java 實(shí)現(xiàn)中出現(xiàn)?!?我強(qiáng)烈懷疑大多數(shù)開發(fā)人員(包括我自己)都做了很多假設(shè),認(rèn)為 for 的范圍 getSeconds()實(shí)際上在 0-59 范圍內(nèi)(含)。 -
它的寬容沒有明顯的理由: “在所有情況下,為這些目的而對(duì)方法給出的論據(jù)不必落在指定的范圍內(nèi); 例如,日期可以指定為 1 月 32 日,并被解釋為 2 月 1 日。” 多久有用一次?

二、為啥要改?
java.util.Date與java.sql.Date類和其提供的API,考慮使用java.time.Instant類或java.time.LocalDateTime類及其提供的API替代。
三、怎么改?
1. 耐心比對(duì)數(shù)據(jù)庫(kù)日期字段和DO的映射
1)確定字段類型
-
如果字段代表日期和時(shí)間,則可能需要使用 LocalDateTime。 -
如果字段僅代表日期,則可能需要使用 LocalDate。 -
如果字段僅代表時(shí)間,則可能需要使用 LocalTime。 -
如果字段需要保存時(shí)間戳(帶時(shí)區(qū)的),則可能需要使用 Instant 或 ZonedDateTime。
2)更新數(shù)據(jù)對(duì)象類
java.time 類型。
2. 將DateUtil中的方法改造
1)替換原來的new Date()和Calendar.getInstance().getTime()
Date nowDate = new Date();
Date nowCalendarDate = Calendar.getInstance().getTime();
java.time 改造后:
// 使用Instant代表一個(gè)時(shí)間點(diǎn),這與Date類似
Instant nowInstant = Instant.now();
// 如果需要用到具體的日期和時(shí)間(例如年、月、日、時(shí)、分、秒)
LocalDateTime nowLocalDateTime = LocalDateTime.now();
// 如果你需要和特定的時(shí)區(qū)交互,可以使用ZonedDateTime
ZonedDateTime nowZonedDateTime = ZonedDateTime.now();
// 如果你需要轉(zhuǎn)換回java.util.Date,你可以這樣做(假設(shè)你的代碼其他部分還需要使用Date)
Date nowFromDateInstant = Date.from(nowInstant);
// 如果需要與java.sql.Timestamp交互
java.sql.Timestamp nowFromInstant = java.sql.Timestamp.from(nowInstant);
-
Instant 表示的是一個(gè)時(shí)間點(diǎn),它是時(shí)區(qū)無關(guān)的,相當(dāng)于舊的 Date 類。它通常用于表示時(shí)間戳。 -
LocalDateTime表示沒有時(shí)區(qū)信息的日期和時(shí)間,它不能直接轉(zhuǎn)換為時(shí)間戳,除非你將其與時(shí)區(qū)結(jié)合使用(例如通過ZonedDateTime)。 -
ZonedDateTime包含時(shí)區(qū)信息的日期和時(shí)間,它更類似于 Calendar,因?yàn)?Calendar 也包含時(shí)區(qū)信息。 -
當(dāng)你需要將 java.time對(duì)象轉(zhuǎn)換回java.util.Date對(duì)象時(shí),可以使用Date.from(Instant)方法。這在你的代碼需要與舊的API或庫(kù)交互時(shí)非常有用。
2)一些基礎(chǔ)的方法改造
public static String dateFormat(Date date, String dateFormat) {
SimpleDateFormat formatter = new SimpleDateFormat(dateFormat);
return formatter.format(date);
}
java.time改造后
public static String dateFormat(LocalDateTime date, String dateFormat) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
return date.format(formatter);
}
public static Date addSecond(Date date, int second) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(13, second);
return calendar.getTime();
}
public static Date addMinute(Date date, int minute) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(12, minute);
return calendar.getTime();
}
public static Date addHour(Date date, int hour) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(10, hour);
return calendar.getTime();
}
public static Date addDay(Date date, int day) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(5, day);
return calendar.getTime();
}
public static Date addMonth(Date date, int month) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(2, month);
return calendar.getTime();
}
public static Date addYear(Date date, int year) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(1, year);
return calendar.getTime();
}
java.time改造后
public static LocalDateTime addSecond(LocalDateTime date, int second) {
return date.plusSeconds(second);
}
public static LocalDateTime addMinute(LocalDateTime date, int minute) {
return date.plusMinutes(minute);
}
public static LocalDateTime addHour(LocalDateTime date, int hour) {
return date.plusHours(hour);
}
public static LocalDateTime addDay(LocalDateTime date, int day) {
return date.plusDays(day);
}
public static LocalDateTime addMonth(LocalDateTime date, int month) {
return date.plusMonths(month);
}
public static LocalDateTime addYear(LocalDateTime date, int year) {
return date.plusYears(year);
}
public static final String[] WEEK_DAY_OF_CHINESE = new String[]{"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
public static String dateToWeek(Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
return WEEK_DAY_OF_CHINESE[cal.get(7) - 1];
}
java.time改造后
public static final String[] WEEK_DAY_OF_CHINESE = new String[]{"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
public static String dateToWeek(LocalDate date) {
DayOfWeek dayOfWeek = date.getDayOfWeek();
return WEEK_DAY_OF_CHINESE[dayOfWeek.getValue() % 7];
}
public static Date getStartTimeOfDay(Date date) {
if (date == null) {
return null;
} 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());
}
}
public static Date getEndTimeOfDay(Date date) {
if (date == null) {
return null;
} 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改造后
public static LocalDateTime getStartTimeOfDay(LocalDateTime date) {
if (date == null) {
return null;
} else {
// 獲取一天的開始時(shí)間,即00:00
return date.toLocalDate().atStartOfDay();
}
}
public static LocalDateTime getEndTimeOfDay(LocalDateTime date) {
if (date == null) {
return null;
} else {
// 獲取一天的結(jié)束時(shí)間,即23:59:59.999999999
return date.toLocalDate().atTime(LocalTime.MAX);
}
}
public static 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改造后
public static Boolean betweenStartAndEnd(Instant nowTime, Instant beginTime, Instant endTime) {
return nowTime.isAfter(beginTime) && nowTime.isBefore(endTime);
}
四、小結(jié)一下
來源|c(diǎn)nblogs.com/wlovet/p/18058514
往期熱門文章:
1、我在代碼里面故意留個(gè)漏洞,違法嗎?
2、一個(gè)現(xiàn)代化輕量級(jí)的跨平臺(tái) Redis 桌面客戶端,支持 Mac、Windows 和 Linux
3、能用到“退休”的 600條 Linux 命令,可以解決日常99%的問題~
4、工作六年,看到這樣的代碼,內(nèi)心五味雜陳...
5、拒絕寫重復(fù)代碼,試試這套開源的 SpringBoot 組件
6、一次生產(chǎn)事故,來來回回搞了一個(gè)月,人麻了!
7、面試官:Git 如何撤回已 Push 的代碼?問倒一大片。。。
8、SpringBoot 如何快速過濾出一次請(qǐng)求的所有日志?
9、千萬不要把 Request 傳遞到異步線程里面!有坑!
10、別再用 offset 和 limit 分頁(yè)了,性能太差!
評(píng)論
圖片
表情
