Java8 新特性(三) - 日期時間物件以及一些其他特性
日期時間物件
關於日期時間的操作可以分為兩種:
- 轉換:與字串的互相轉換,與時間戳的互相轉換
- 計算:計算兩個時間點之間的間隔、時間點與時間段的計算(計算下週N、下個月D日、去年M月D日等等)
Java8 提供了三個類:LocalDate
、LocalTime
、LocalDateTime
,它們的形式如 2020-01-01
、12:30:00
、2020-01-01 12:30:00
建立物件
獲取類物件的方法非常非常簡單
LocalDate now = LocalDate.now(); LocalDate ld = LocalDate.of(2019, 1, 1); // 獲取年月日 now.getYear(); now.getMonthValue(); // 如果你呼叫了 now.getMonth() ,那麼它將返回給你一個大寫的英文月份單詞 now.getDayOfMonth(); // 顧名應該思義 getDayOfWeek(); getDayOfYear(); // 設定年月日 LocalDate ld1 = ld.withYear(2021); // 2021-01-01 LocalDate ld2 = ld.withMonth(12); // 2019-12-01 LocalDate ld3 = ld.withDayOfMonth(12); // 2019-12-12 // 你可能會納悶,既然是設定,為什麼不用單詞 set 呢,而用 with // 因為,set 操作一般是改變呼叫物件本身,沒有返回值; // 而 with 是在呼叫物件基礎上另外建立一個新物件,設定好值後返回,沒有改變呼叫物件 // 如果你是那個打破砂鍋的孩子,你可能會問:為什麼不能改變呼叫物件? // 因為 LocalDate 是 final 修飾的(final 人稱 Java 界的自宮之刀) // 從物理的角度來講,目前人類無法改變時間(穿越) // 如果你有 ld.withMonth(13) 這種反人類曆法的操作,當然是會丟擲異常的
LocalTime
和LocalDateTime
都有類似於LocalDate
的方法,這裡就不一一列舉了(因為我感覺自己越來越像 api 文件了)Java8 API 官方文件直通車
轉換
日期時間物件 和 字串 之間的互相轉換:
// LocalDateTime 物件 -> 字串 DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); LocalDateTime now = LocalDateTime.now(); String dateTimeStr = now.format(dtf); System.out.println(dateTimeStr); // 字串 -> LocalDateTime 物件 String str = "2022-01-30 12:15:20"; LocalDateTime dateTime = LocalDateTime.parse(str, dtf); System.out.println(dateTime);
DateTimeFormatter 類還提供一些現成的 formatter ,比如
DateTimeFormatter.BASIC_ISO_DATE ==> DateTimeFormatter.ofPattern("yyyyMMdd")
DateTimeFormatter.ISO_LOCAL_DATE ==> DateTimeFormatter.ofPattern("yyyy-MM-dd")
// 更多 formatter 可以 api 文件中查詢
學習的本質,不在於記住哪些知識,而在於它觸發了你的思考。—— 邁克爾·桑德爾
日期時間 和 時間戳 之間的互相轉換:
// LocalDateTime 物件 -> 時間戳
LocalDateTime now = LocalDateTime.now();
// 獲取系統預設時區
ZoneId systemDefaultZoneId = ZoneId.systemDefault();
Instant instant = now.atZone(systemDefaultZoneId).toInstant();
long timestamp = instant.toEpochMilli();
System.out.println(timestamp);
// 時間戳 -> LocalDateTime 物件
long timestamp2 = 1578919583784L;
Instant instant2 = Instant.ofEpochMilli(timestamp2);
LocalDateTime dateTime2 = LocalDateTime.ofInstant(instant2, systemDefaultZoneId);
System.out.println(dateTime2);
“我不明白為什麼要把時間戳搞得這麼麻煩!”
另外:java.util.Date
與 java.time.LocalDateTime
之間的轉換需要通過 Instant
實現,它倆都沒有提供直接的轉換方法
// 獲取系統預設時區
ZoneId systemDefaultZoneId = ZoneId.systemDefault();
// Date 轉為 LocalDateTime
Date date3 = new Date();
Instant instant3 = date3.toInstant();
LocalDateTime localDateTime3 = LocalDateTime.ofInstant(instant3, systemDefaultZoneId);
// LocalDateTime 轉為 Date
Instant instant4 = now.atZone(systemDefaultZoneId).toInstant();
Date date4 = Date.from(instant4);
還有:LocalDateTime
可以由 LocalDate
和 LocalTime
組成,也可以拆分成它倆
LocalDate nowLocalDate = LocalDate.now();
LocalTime nowLocalTime = LocalTime.now();
LocalDateTime nowLocalDateTime = LocalDateTime.of(nowLocalDate, nowLocalTime);
nowLocalDateTime.toLocalDate();
nowLocalDateTime.toLocalTime();
計算
計算時間點與時間點之間的間隔:
// 計算日期時間之間的間隔
LocalTime startTime = LocalTime.now();
LocalTime endTime = startTime.plusHours(1).plusMinutes(50);
Duration duration = Duration.between(startTime, endTime);
// 間隔秒數
duration.getSeconds();
// 間隔天數
duration.toDays();
// 間隔小時數
duration.toHours();
// 間隔分鐘數
duration.toMinutes();
Duration.between(start, end)
的引數可以是 LocalDateTime
、LocalTime
它只會返回一個整數(舍掉小數後的整數,等同於 floor()),不會返回 1小時50分鐘
這樣的形式
如果你想要 y年M個月d天 H小時m分鐘s秒
這種形式,或許你自己動手組裝一下了
// 計算日期之間的間隔
LocalDate startDate = LocalDate.now();
LocalDate endDate = LocalDate.of(2031, 1, 1);
Period pe = Period.between(startDate, endDate);
pe.getYears();
pe.getMonths();
pe.getDays();
時間點與時間段的計算:
public LocalDateTime plusYears(long years) {
LocalDate newDate = date.plusYears(years);
return with(newDate, time);
}
public LocalDateTime plusMonths(long months) {
LocalDate newDate = date.plusMonths(months);
return with(newDate, time);
}
public LocalDateTime plusWeeks(long weeks) {
LocalDate newDate = date.plusWeeks(weeks);
return with(newDate, time);
}
public LocalDateTime plusDays(long days) {
LocalDate newDate = date.plusDays(days);
return with(newDate, time);
}
public LocalDateTime plusHours(long hours) {
return plusWithOverflow(date, hours, 0, 0, 0, 1);
}
public LocalDateTime plusMinutes(long minutes) {
return plusWithOverflow(date, 0, minutes, 0, 0, 1);
}
public LocalDateTime plusSeconds(long seconds) {
return plusWithOverflow(date, 0, 0, seconds, 0, 1);
}
public LocalDateTime plusNanos(long nanos) {
return plusWithOverflow(date, 0, 0, 0, nanos, 1);
}
public LocalDateTime minusYears(long years) {
return (years == Long.MIN_VALUE ? plusYears(Long.MAX_VALUE).plusYears(1) : plusYears(-years));
}
public LocalDateTime minusMonths(long months) {
return (months == Long.MIN_VALUE ? plusMonths(Long.MAX_VALUE).plusMonths(1) : plusMonths(-months));
}
public LocalDateTime minusWeeks(long weeks) {
return (weeks == Long.MIN_VALUE ? plusWeeks(Long.MAX_VALUE).plusWeeks(1) : plusWeeks(-weeks));
}
public LocalDateTime minusDays(long days) {
return (days == Long.MIN_VALUE ? plusDays(Long.MAX_VALUE).plusDays(1) : plusDays(-days));
}
public LocalDateTime minusHours(long hours) {
return plusWithOverflow(date, hours, 0, 0, 0, -1);
}
public LocalDateTime minusMinutes(long minutes) {
return plusWithOverflow(date, 0, minutes, 0, 0, -1);
}
public LocalDateTime minusSeconds(long seconds) {
return plusWithOverflow(date, 0, 0, seconds, 0, -1);
}
public LocalDateTime minusNanos(long nanos) {
return plusWithOverflow(date, 0, 0, 0, nanos, -1);
}
看吧,加減年數、月數、天數、小時數、分鐘數、秒數、毫秒數都有
想怎麼用就怎麼用,舉個小例子:
LocalDateTime now = LocalDateTime.now();
// 30年後的今天(我還要上班,還沒退休)
LocalDateTime after30Years = now.plusYears(30L);
System.out.println(after30Years);
// 347個月後(我就能還清貸款了 ╥﹏╥)
LocalDateTime after348Months = now.plusMonths(347L);
System.out.println(after348Months);
// 11天后(就是除夕了)
LocalDateTime after10Days = now.plusDays(10L);
System.out.println(after10Days);
// 8小時前(我在上班)
LocalDateTime before8Hours = now.minusHours(8L);
System.out.println(before8Hours);
// 3分鐘前(我開始聽 Let it go 這首歌)
LocalDateTime before3Before = now.minusMinutes(3L);
System.out.println(before3Before);
// 10秒前(寫下下面這條程式碼)
LocalDateTime before10Second = now.minusSeconds(10L);
System.out.println(before10Second);
其實不用區分什麼加減的,也可以用 plusXxx 做減法,只要傳入負數引數就行了
另外這裡還有一類需求,比如:
明年的感恩節是哪天?(每年11月的第四個星期四為感恩節)
下週五是哪天?
這就要用到時間校正器 TemporalAdjuster
這裡容我先介紹一下 TemporalAdjuster
,它是一個函式式介面,只有一個方法 adjustInto
@FunctionalInterface
public interface TemporalAdjuster {
Temporal adjustInto(Temporal temporal);
}
Temporal
是一個介面,LocalDateTime
、LocalDate
、LocalTime
都是它的實現類
在 LocalDateTime
、LocalDate
、 LocalTime
中都有 with(TemporalAdjuster adjuster)
這個方法用來實現上面提到的另類需求。
// 下週五
LocalDateTime now = LocalDateTime.now();
LocalDateTime nextFriday = now.with(dt -> {
// dt 是 `Temporal` 物件,但實質上是呼叫物件的型別
LocalDateTime dateTime = (LocalDateTime) dt;
// 非常可惜,沒有 withDayOfWeek() 這個方法,要不然就會非常方便了
int dayOfWeekValue = dateTime.getDayOfWeek().getValue();
int fridayValue = DayOfWeek.FRIDAY.getValue();
return dateTime.plusWeeks(1L)
.plusDays(fridayValue - dayOfWeekValue);
});
System.out.println(nextFriday);
// 明年的感恩節(明年11月第四個星期四)
LocalDate thanksGivingDay = LocalDate.now().with( t -> {
LocalDate d = (LocalDate) t;
// 明年11月1日
LocalDate newDate = d.plusYears(1L).withMonth(11).withDayOfMonth(1);
int dayOfWeekValue = newDate.getDayOfWeek().getValue();
int thursdayValue = DayOfWeek.THURSDAY.getValue();
long plusWeeks = dayOfWeekValue > thursdayValue ? 4L : 3L;
return newDate.plusWeeks(plusWeeks)
.plusDays(thursdayValue - dayOfWeekValue);
});
System.out.println(thanksGivingDay);
其實 TemporalAdjusters
提供了許多 TemporalAdjuster
物件,就像上一節 Stream 中 Collectors
之於 Collector
一樣 。
使用 TemporalAdjusters
能夠十分方便的實現上面的需求
// 下個週五
LocalDateTime nextFriday = LocalDateTime.now().with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
System.out.println(nextFriday);
// 明年的感恩節(明年11月第四個星期四)
LocalDate date = LocalDate.now().plusYears(1L).withMonth(11);
LocalDate thanksGivingDay = date.with(TemporalAdjusters.dayOfWeekInMonth(4, DayOfWeek.THURSDAY));
System.out.println(thanksGivingDay);
注意:
TemporalAdjusters.next(DayOfWeek day)
方法返回的是 接下來第一個週五,並不是我們一般理解的 下週五,比如說:今天 2020-01-13(週一),那麼返回的就是 2020-01-17 四天後的週五。
另外 TemporalAdjusters
並不止提供了上面這2個方法,還有很多其他方法, API 文件 中給出了足夠多的例子,一看就明白了。
建議有興趣的同學,去閱讀一些原始碼,參考 Java8 程式碼邏輯,然後用其他程式語言實現相同的日期時間操作,因為在其他程式設計中(比如 javaScript)也會經常用到日期時間的操作
其他特性
羅列出來表示我知道他們,但不表示我理解他們,所以 Let It Go!
介面中的預設方法
介面中的靜態方法
Optional 類
Optional<String> op = Optional.of(str);
它是用來標識這個變數有可能為空。
如果一個變數有可能為空,Java8 之前我們每次使用這個變數時,都必須判斷它是否為空。現在也是!!
Optional
並不能避免空指標異常,僅僅是表示標識變數可能為空。打個比方:
前面一條路上埋了地雷,但從表面上完全看不出來,除非我們走一步都扔石頭試一下,否則說不準哪一步就炸了。而 Optional
就是用來標識地雷位置的,我們知道了哪個位置有雷,就會繞著走,從而能夠安全通過
另外 Optional 還提供了一個設定預設值的功能,挺好玩的。
Integer pageSize = null;
// 以前我們設定預設值
//pageSize = pageSize == null ? 10 : pageSize;
//System.out.println(pageSize);
// 使用 Optional 設定預設值
pageSize = Optional.ofNullable(pageSize)
.orElse(20);
System.out.println(pageSize);
// 自定義預設值
Integer defaultPageSize = Optional.ofNullable(pageSize)
.orElseGet(() -> {
return new Integer(50);
});
結語
Java8 新特性系列隨便到此就結束了。
最最關鍵的,還是多看 Java 官方 API 文件!!
我在想可能我們被矯枉過正了。各種各樣技術群最多的回答都是:去問百度!! 百度全知道嗎?!
說實在的,在今天這個時代,是個人都能在網路上發表文章言論,然後大家再互相轉載,假的都能成為真的!
沒有經過驗證就轉載的;在當時有效,現在過時了的;隨便在文章中一個轉載連結的 ... 比比皆是
這裡有一個開眼的短視訊,介紹 虛假新聞是如何在傳播
最惱人的是,百度搜索一個關鍵詞 XXX,點進去一篇部落格文章,結果文章內容根本就沒有這個關鍵詞,那麼關鍵詞到底在哪呢?
關鍵詞在網站的推薦文章列表標題裡,所以百度爬取到了,顯示在搜尋結果裡了。真是日了狗了!!
百度搜到的部落格能別看就別看,看也要看那些經常更新,比較靠譜的。
靠譜的學習途徑:
- 官方最新 API 文件
- 書籍/視訊(要注意權威性和時效性)
- 經常更新靠譜的部落格
不要過於信任搜尋引擎!!
輕易不要使用搜索引擎!!