Java8 新特性(三) - 日期時(shí)間對象以及一些其他特性

日期時(shí)間對象

關(guān)于日期時(shí)間的操作可以分為兩種:

  • 轉(zhuǎn)換:與字符串的互相轉(zhuǎn)換垄琐,與時(shí)間戳的互相轉(zhuǎn)換
  • 計(jì)算:計(jì)算兩個(gè)時(shí)間點(diǎn)之間的間隔勘天、時(shí)間點(diǎn)與時(shí)間段的計(jì)算(計(jì)算下周N滩报、下個(gè)月D日宝惰、去年M月D日等等)

Java8 提供了三個(gè)類:LocalDateLocalTimeLocalDateTime庇配,它們的形式如 2020-01-01斩跌、12:30:002020-01-01 12:30:00

創(chuàng)建對象

獲取類對象的方法非常非常簡單

LocalDate now = LocalDate.now();
LocalDate ld = LocalDate.of(2019, 1, 1);
// 獲取年月日
now.getYear();
now.getMonthValue();    // 如果你調(diào)用了 now.getMonth() 捞慌,那么它將返回給你一個(gè)大寫的英文月份單詞
now.getDayOfMonth();
// 顧名應(yīng)該思義
getDayOfWeek();
getDayOfYear(); 

// 設(shè)置年月日
LocalDate ld1 = ld.withYear(2021);      // 2021-01-01
LocalDate ld2 = ld.withMonth(12);       // 2019-12-01
LocalDate ld3 = ld.withDayOfMonth(12);  // 2019-12-12
// 你可能會納悶耀鸦,既然是設(shè)置,為什么不用單詞 set 呢啸澡,而用 with
// 因?yàn)樾涠瑂et 操作一般是改變調(diào)用對象本身,沒有返回值嗅虏;
// 而 with 是在調(diào)用對象基礎(chǔ)上另外創(chuàng)建一個(gè)新對象洛姑,設(shè)置好值后返回,沒有改變調(diào)用對象

// 如果你是那個(gè)打破砂鍋的孩子皮服,你可能會問:為什么不能改變調(diào)用對象楞艾?
// 因?yàn)?LocalDate 是 final 修飾的(final 人稱 Java 界的自宮之刀)
// 從物理的角度來講,目前人類無法改變時(shí)間(穿越)

// 如果你有 ld.withMonth(13) 這種反人類歷法的操作龄广,當(dāng)然是會拋出異常的

LocalTimeLocalDateTime 都有類似于 LocalDate 的方法硫眯,這里就不一一列舉了(因?yàn)槲腋杏X自己越來越像 api 文檔了)

Java8 API 官方文檔直通車

轉(zhuǎn)換

日期時(shí)間對象 和 字符串 之間的互相轉(zhuǎn)換:

// 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 類還提供一些現(xiàn)成的 formatter ,比如

DateTimeFormatter.BASIC_ISO_DATE ==> DateTimeFormatter.ofPattern("yyyyMMdd")
DateTimeFormatter.ISO_LOCAL_DATE ==> DateTimeFormatter.ofPattern("yyyy-MM-dd")
// 更多 formatter 可以 api 文檔中查詢

學(xué)習(xí)的本質(zhì)蜀细,不在于記住哪些知識,而在于它觸發(fā)了你的思考戈盈〉煜危—— 邁克爾·桑德爾

日期時(shí)間 和 時(shí)間戳 之間的互相轉(zhuǎn)換:

// LocalDateTime 對象 -> 時(shí)間戳
LocalDateTime now = LocalDateTime.now();
// 獲取系統(tǒng)默認(rèn)時(shí)區(qū)
ZoneId systemDefaultZoneId = ZoneId.systemDefault();
Instant instant = now.atZone(systemDefaultZoneId).toInstant();
long timestamp = instant.toEpochMilli();
System.out.println(timestamp);


// 時(shí)間戳 -> LocalDateTime 對象
long timestamp2 = 1578919583784L;
Instant instant2 = Instant.ofEpochMilli(timestamp2);
LocalDateTime dateTime2 = LocalDateTime.ofInstant(instant2, systemDefaultZoneId);
System.out.println(dateTime2);

“我不明白為什么要把時(shí)間戳搞得這么麻煩!”

另外:java.util.Datejava.time.LocalDateTime 之間的轉(zhuǎn)換需要通過 Instant 實(shí)現(xiàn)塘娶,它倆都沒有提供直接的轉(zhuǎn)換方法

// 獲取系統(tǒng)默認(rèn)時(shí)區(qū)
ZoneId systemDefaultZoneId = ZoneId.systemDefault();
// Date 轉(zhuǎn)為 LocalDateTime
Date date3 = new Date();
Instant instant3 = date3.toInstant();
LocalDateTime localDateTime3 = LocalDateTime.ofInstant(instant3, systemDefaultZoneId);

// LocalDateTime 轉(zhuǎn)為 Date
Instant instant4 = now.atZone(systemDefaultZoneId).toInstant();
Date date4 = Date.from(instant4);

還有:LocalDateTime 可以由 LocalDateLocalTime 組成归斤,也可以拆分成它倆

LocalDate nowLocalDate = LocalDate.now();
LocalTime nowLocalTime = LocalTime.now();
LocalDateTime nowLocalDateTime = LocalDateTime.of(nowLocalDate, nowLocalTime);

nowLocalDateTime.toLocalDate();
nowLocalDateTime.toLocalTime();

計(jì)算

計(jì)算時(shí)間點(diǎn)與時(shí)間點(diǎn)之間的間隔:

// 計(jì)算日期時(shí)間之間的間隔
LocalTime startTime = LocalTime.now();
LocalTime endTime = startTime.plusHours(1).plusMinutes(50);
Duration duration = Duration.between(startTime, endTime);

// 間隔秒數(shù)
duration.getSeconds();
// 間隔天數(shù)
duration.toDays();
// 間隔小時(shí)數(shù)
duration.toHours();
// 間隔分鐘數(shù)
duration.toMinutes();

Duration.between(start, end) 的參數(shù)可以是 LocalDateTimeLocalTime

它只會返回一個(gè)整數(shù)(舍掉小數(shù)后的整數(shù)刁岸,等同于 floor())脏里,不會返回 1小時(shí)50分鐘 這樣的形式
如果你想要 y年M個(gè)月d天 H小時(shí)m分鐘s秒 這種形式,或許你自己動(dòng)手組裝一下了

// 計(jì)算日期之間的間隔
LocalDate startDate = LocalDate.now();
LocalDate endDate = LocalDate.of(2031, 1, 1);
Period pe = Period.between(startDate, endDate);
pe.getYears();
pe.getMonths();
pe.getDays();

時(shí)間點(diǎn)與時(shí)間段的計(jì)算:

    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);
    }

看吧虹曙,加減年數(shù)迫横、月數(shù)、天數(shù)酝碳、小時(shí)數(shù)矾踱、分鐘數(shù)、秒數(shù)疏哗、毫秒數(shù)都有

想怎么用就怎么用呛讲,舉個(gè)小例子:

LocalDateTime now = LocalDateTime.now();

// 30年后的今天(我還要上班,還沒退休)
LocalDateTime after30Years = now.plusYears(30L);
System.out.println(after30Years);

// 347個(gè)月后(我就能還清貸款了 ╥﹏╥)
LocalDateTime after348Months = now.plusMonths(347L);
System.out.println(after348Months);

// 11天后(就是除夕了)
LocalDateTime after10Days = now.plusDays(10L);
System.out.println(after10Days);

// 8小時(shí)前(我在上班)
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);

其實(shí)不用區(qū)分什么加減的,也可以用 plusXxx 做減法贝搁,只要傳入負(fù)數(shù)參數(shù)就行了

另外這里還有一類需求吗氏,比如:

明年的感恩節(jié)是哪天?(每年11月的第四個(gè)星期四為感恩節(jié))
下周五是哪天雷逆?

這就要用到時(shí)間校正器 TemporalAdjuster
這里容我先介紹一下 TemporalAdjuster弦讽,它是一個(gè)函數(shù)式接口,只有一個(gè)方法 adjustInto

@FunctionalInterface
public interface TemporalAdjuster {
    Temporal adjustInto(Temporal temporal);
}

Temporal 是一個(gè)接口关面,LocalDateTime 坦袍、LocalDateLocalTime 都是它的實(shí)現(xiàn)類

LocalDateTime 等太、LocalDate 捂齐、 LocalTime 中都有 with(TemporalAdjuster adjuster) 這個(gè)方法用來實(shí)現(xiàn)上面提到的另類需求。

// 下周五
LocalDateTime now = LocalDateTime.now();
LocalDateTime nextFriday = now.with(dt -> {
    // dt 是 `Temporal` 對象缩抡,但實(shí)質(zhì)上是調(diào)用對象的類型
    LocalDateTime dateTime = (LocalDateTime) dt;
    // 非车煲耍可惜,沒有 withDayOfWeek() 這個(gè)方法瞻想,要不然就會非常方便了
    int dayOfWeekValue = dateTime.getDayOfWeek().getValue();
    int fridayValue = DayOfWeek.FRIDAY.getValue();
    return dateTime.plusWeeks(1L)
                .plusDays(fridayValue - dayOfWeekValue);
});
System.out.println(nextFriday);

// 明年的感恩節(jié)(明年11月第四個(gè)星期四)
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);

其實(shí) TemporalAdjusters 提供了許多 TemporalAdjuster 對象压真,就像上一節(jié) Stream 中 Collectors 之于 Collector 一樣 。

使用 TemporalAdjusters 能夠十分方便的實(shí)現(xiàn)上面的需求

// 下個(gè)周五
LocalDateTime nextFriday = LocalDateTime.now().with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
System.out.println(nextFriday);

// 明年的感恩節(jié)(明年11月第四個(gè)星期四)
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) 方法返回的是 接下來第一個(gè)周五蘑险,并不是我們一般理解的 下周五滴肿,比如說:今天 2020-01-13(周一),那么返回的就是 2020-01-17 四天后的周五佃迄。

另外 TemporalAdjusters 并不止提供了上面這2個(gè)方法泼差,還有很多其他方法, API 文檔 中給出了足夠多的例子呵俏,一看就明白了堆缘。

建議有興趣的同學(xué),去閱讀一些源碼普碎,參考 Java8 代碼邏輯吼肥,然后用其他編程語言實(shí)現(xiàn)相同的日期時(shí)間操作,因?yàn)樵谄渌幊讨校ū热?javaScript)也會經(jīng)常用到日期時(shí)間的操作

其他特性

羅列出來表示我知道他們麻车,但不表示我理解他們缀皱,所以 Let It Go!

接口中的默認(rèn)方法

接口中的靜態(tài)方法

Optional 類

Optional<String> op = Optional.of(str);

它是用來標(biāo)識這個(gè)變量有可能為空动猬。

如果一個(gè)變量有可能為空唆鸡,Java8 之前我們每次使用這個(gè)變量時(shí),都必須判斷它是否為空枣察。現(xiàn)在也是U肌燃逻!

Optional 并不能避免空指針異常,僅僅是表示標(biāo)識變量可能為空臂痕。打個(gè)比方:

前面一條路上埋了地雷伯襟,但從表面上完全看不出來,除非我們走一步都扔石頭試一下握童,否則說不準(zhǔn)哪一步就炸了姆怪。而 Optional就是用來標(biāo)識地雷位置的,我們知道了哪個(gè)位置有雷澡绩,就會繞著走稽揭,從而能夠安全通過

另外 Optional 還提供了一個(gè)設(shè)置默認(rèn)值的功能,挺好玩的肥卡。

Integer pageSize = null;
// 以前我們設(shè)置默認(rèn)值
//pageSize = pageSize == null ? 10 : pageSize;
//System.out.println(pageSize);

// 使用 Optional 設(shè)置默認(rèn)值
pageSize = Optional.ofNullable(pageSize)
    .orElse(20);
System.out.println(pageSize);

// 自定義默認(rèn)值
Integer defaultPageSize = Optional.ofNullable(pageSize)
        .orElseGet(() -> {
            return new Integer(50);
        });

結(jié)語

Java8 新特性系列隨便到此就結(jié)束了溪掀。

最最關(guān)鍵的,還是多看 Java 官方 API 文檔2郊揪胃!

我在想可能我們被矯枉過正了。各種各樣技術(shù)群最多的回答都是:去問百度7兆痢喊递! 百度全知道嗎?阳似!

說實(shí)在的骚勘,在今天這個(gè)時(shí)代,是個(gè)人都能在網(wǎng)絡(luò)上發(fā)表文章言論撮奏,然后大家再互相轉(zhuǎn)載俏讹,假的都能成為真的!
沒有經(jīng)過驗(yàn)證就轉(zhuǎn)載的挽荡;在當(dāng)時(shí)有效藐石,現(xiàn)在過時(shí)了的即供;隨便在文章中一個(gè)轉(zhuǎn)載鏈接的 ... 比比皆是

這里有一個(gè)開眼的短視頻定拟,介紹 虛假新聞是如何在傳播

最惱人的是,百度搜索一個(gè)關(guān)鍵詞 XXX逗嫡,點(diǎn)進(jìn)去一篇博客文章青自,結(jié)果文章內(nèi)容根本就沒有這個(gè)關(guān)鍵詞,那么關(guān)鍵詞到底在哪呢驱证?
關(guān)鍵詞在網(wǎng)站的推薦文章列表標(biāo)題里延窜,所以百度爬取到了,顯示在搜索結(jié)果里了抹锄。真是日了狗了D嫒稹荠藤!

百度搜到的博客能別看就別看,看也要看那些經(jīng)常更新获高,比較靠譜的哈肖。

靠譜的學(xué)習(xí)途徑:

  1. 官方最新 API 文檔
  2. 書籍/視頻(要注意權(quán)威性和時(shí)效性)
  3. 經(jīng)常更新靠譜的博客

輕易不要百度!念秧!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末淤井,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子摊趾,更是在濱河造成了極大的恐慌币狠,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砾层,死亡現(xiàn)場離奇詭異漩绵,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)梢为,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門渐行,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人铸董,你說我怎么就攤上這事祟印。” “怎么了粟害?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵蕴忆,是天一觀的道長。 經(jīng)常有香客問我悲幅,道長套鹅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任汰具,我火速辦了婚禮卓鹿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘留荔。我一直安慰自己吟孙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布聚蝶。 她就那樣靜靜地躺著杰妓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪碘勉。 梳的紋絲不亂的頭發(fā)上巷挥,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機(jī)與錄音验靡,去河邊找鬼倍宾。 笑死雏节,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的高职。 我是一名探鬼主播矾屯,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼初厚!你這毒婦竟也來了件蚕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤产禾,失蹤者是張志新(化名)和其女友劉穎排作,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體亚情,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡妄痪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了楞件。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衫生。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖土浸,靈堂內(nèi)的尸體忽然破棺而出罪针,到底是詐尸還是另有隱情,我是刑警寧澤黄伊,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布泪酱,位于F島的核電站,受9級特大地震影響还最,放射性物質(zhì)發(fā)生泄漏墓阀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一拓轻、第九天 我趴在偏房一處隱蔽的房頂上張望斯撮。 院中可真熱鬧,春花似錦扶叉、人聲如沸勿锅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽粱甫。三九已至泳叠,卻和暖如春作瞄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背危纫。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工宗挥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乌庶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓契耿,卻偏偏與公主長得像瞒大,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子搪桂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Java8新特性的功能已經(jīng)更新了不少篇幅了透敌,今天重點(diǎn)講解時(shí)間日期庫中DateTime相關(guān)處理。同樣的踢械,如果你現(xiàn)在依...
    程序新視界閱讀 347評論 0 1
  • 前言:Java 8 已經(jīng)發(fā)布很久了酗电,很多報(bào)道表明Java 8 是一次重大的版本升級。在Java Code Geek...
    糖寶_閱讀 1,322評論 1 1
  • 1内列、LocalDate撵术、LocalTime、Instant话瞧、Duration嫩与、Period 1)使用LocalDa...
    夏與清風(fēng)閱讀 562評論 0 0
  • 清明的整個(gè)假期陪了寶寶。 清明那天祭拜父親交排,第二題天回老家本意是看梨花划滋,梨花敗了,改看了桃花埃篓。今兒去了一二九師緬懷...
    簡小單_閱讀 257評論 0 2
  • 學(xué)渣顧名思義就是差等生古毛。這類人在處于這個(gè)標(biāo)簽之下,不同的人有不同的做法都许。一小部分人可能會拼了命地?cái)[脫這個(gè)陰影稻薇,而往...
    逸軒君閱讀 487評論 0 3