1. 程式人生 > >都9012了,Java8日期時間API你還沒有掌握?

都9012了,Java8日期時間API你還沒有掌握?

工作這麼久了,對於Java中時間日期的操作一直很蛋疼,一會用Date,一會用Calendar一會用LocalDateTime,始終沒有認真總結過它們的聯絡與區別。迷迷糊糊用了好幾年了,今天終於搞清楚了!

一,Java8日期時間API產生的前因後果

1.1 為什麼要重新定義一套日期時間API

  • 操作不方便:java中最初的Date不能直接對指定欄位進行加減操作也不支援國際化,後來新增了Calendar,但是Calendar又不支援格式化操作,需要轉換成Date再進行格式化,總之一直在填坑,使用起來一點都不夠優雅。
  • 執行緒不安全:Date,Caleandar,SimpleDateFormat都是可變的,執行緒不安全的,所以你需要編寫額外的程式碼處理執行緒安全問題。

1.2 Java8重新定義

  • 對時間日期相關操作進行細分:時間,日期,日期&時間,時間戳,時間段,日期段,格式化等
  • 所有類都是不可變的,執行緒安全
  • 相容舊的日期時間

1.3Java8相容就版本的Date,同時也規範了日期時間的轉換流程。

一,給人讀的( LocalDateTime & LocalDate & LocalTime)

java8中將時間和日期進行的區分,用LocalDateTime表示日期和時間,LocalDate用來表示日期而LocalTime表示時間。內部實現也非常好理解,LocalDateTime = LocalDate + LocalTime,並且他們的內部api也一致,所以筆者就結合工作中的經驗,介紹他們最常見的用法。

1.1 獲取當前時間

LocalDateTime localDateTime = LocalDateTime.now();
// 列印結果: 2019-12-02T22:09:20.503

1.2 獲取指定時間

// 獲取 2019年12月02號 23 : 59 : 59 
LocalDateTime localDateTime2 = LocalDateTime.of(2019, 12, 2, 23, 59, 59);
// 列印結果: 2019-12-02T13:20:20

1.3 日期/時間加減操作

// localDateTime2的基礎上加1天零1s
LocalDateTime localDateTime3 = localDateTime2.plusDays(1).plusSeconds(1);
// 列印結果:2019-12-04T00:00

1.4 獲取指定的欄位(年月日時分秒,納秒,不支援毫秒)

System.out.println("現在是: " + localDateTime.getYear() + " 年中的第 " + localDateTime.getDayOfYear() +" 天");
// 列印結果:現在是: 2019 年中的第 336 天 
// 畫外音: 快過年了呀,感覺這一年又沒啥收穫

二,給計算機讀的(Instant)

小知識:地球上不同地區經度不同會劃分時區,以零度經線上為準(格林尼治天文臺舊址,UTC時區)為準,將地球上各個部分分為了24個時區。向西走,每過一個時區,就要把表撥慢1個小時;同理每向東走一個時區,就要把表撥快1個小時。最後,中國處於東8區。

2.1 獲取UTC時間(格林尼治時間)

Instant instant = Instant.now();
// 列印結果: 2019-12-02T14:31:41.661Z

2.2 獲取北京時間(東8區)

// OffsetTime表示有時差的時間,除了UTC時間,都是OffsetTime
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
// 列印結果: 2019-12-02T22:31:41.661+08:00

2.3 獲取毫秒數(1970-01-01T00:00:00Z開始計算)

long epochMilli = instant.toEpochMilli()
// 列印結果:1575297101661

2.4 定義時間戳

Instant instant1 = Instant.ofEpochSecond(59);
// 列印結果: 1970-01-01T00:00:59Z
instant2 = instant1.plusSeconds(99)
// 列印結果:2019-12-02T14:43:00.402Z

三, 時間間隔(Duration)

3.1 計算日期間隔(引數位置影響結果哦)

Instant instant1 = Instant.now();
Instant instant2 = instant1.plusSeconds(99);

Duration duration1 = Duration.between(instant1, instant2);
Duration duration2 = Duration.between(instant2, instant1);
// 列印結果 duration1:PT1M39S
// 列印結果 duration2:PT-1M-39S

long duration1Seconds = duration1.getSeconds();
long duration2Seconds = duration1.getSeconds();
// 列印結果 duration1Seconds: 90
// 列印結果 duration2Seconds: -90

3.2 操作時間間隔

Duration duration3 = duration1.plusDays(1);
// 列印結果:PT24H1M39S

注意 : 僅支援時間操作(Instant, LocalTime,LocalDateTime),不支援日期(LocalDate)

四,日期間隔(Period)

LocalDate localDate1 = LocalDate.now();
LocalDate localDate2 = localDate1.plusDays(1);
Period period = Period.between(localDate1, localDate2);
long days =  period.getDays();
// 列印結果 peroid: P1D
// 列印結果 days: 1

五,日期/時間校正器(TemporalAdjuster)

5.1 獲取指定日期或時間

LocalDateTime localDateTime1 = LocalDateTime.now();
LocalDateTime localDateTime2 = localDateTime.withDayOfMonth(20);
// 列印結果 localDateTime1:2019-12-02T22:57:47.674
// 列印結果 localDateTime2:2019-12-20T22:57:47.674

5.2 獲取下一個固定日期(下一個星期天)

LocalDateTime localDateTime3 = localDateTime.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
// 列印結果 localDateTime33:2019-12-08T23:00:43.101

5.3 自定義矯正器

// 獲取下一個工作日
    LocalDateTime localDateTime4 = localDateTime.with((tempDateTime) -> {
            LocalDateTime localDateTime5 = (LocalDateTime) tempDateTime;
            DayOfWeek dayOfWeek = localDateTime5.getDayOfWeek();
            if (dayOfWeek.equals(DayOfWeek.FRIDAY)) {
                    return localDateTime5.plusDays(3);
            } else if (dayOfWeek.equals(DayOfWeek.SATURDAY)) {
                    return localDateTime5.plusDays(2);
            } else {
                    return localDateTime5.plusDays(1);
            }
    });
// 列印結果 localDateTime4:2019-12-03T23:00:43.101

六,日期時間格式化

DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime localDateTime = LocalDateTime.now();
String dateStr =dateTimeFormatter.format(localDateTime);
// 列印結果: 2019-12-02 23:08:55
LocalDateTime localDateTime2 = LocalDateTime.parse(dateStr, dateTimeFormatter);
// 列印結果: 2019-12-02T23:08:55
LocalDate localDate = LocalDate.parse(dateStr, dateTimeFormatter);
// 列印結果: 2019-12-02

七,基於Instant進行轉換

java8api對於時間戳,日期時間以及老版本的Date物件之間的轉換也進行了相容和適配,所有的轉換操作都可以基於Instant物件進行。由於LocalDate,LocalTime和LocalDateTime三個類的操作完全一樣,所以下文仍使用LocalDateTime演示。

7.1 時間戳轉LocalDate,LocalDate,LocalDateTime

long timestamp = Instant.now().toEpochMilli();
LocalDateTime localDateTime = Instant.ofEpochMilli(timestamp).atOffset(ZoneOffset.ofHours(8)).toLocalDateTime();
// 列印結果:2019-12-02T23:20:25.791

7.2 LocalDate,LocalDate,LocalDateTime轉時間戳

LocalDateTime localDateTime = LocalDateTime.now();
long timestamp = localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
// 列印結果:1575300368099 

7.3 相容就版本Date

LocalDateTime localDateTime3 = LocalDateTime.now();Date date = 
Date.from(localDateTime.atZone(ZoneOffset.ofHours(8)).toInstant());LocalDateTime localDateTime4  = 
localDateTime3.atZone(ZoneOffset.ofHours(8)).toLocalDateTime();
// 列印結果 date:Mon Dec 02 23:32:53 CST 2019
// 列印結果 lcoalDateTime4:2019-12-02T23:32:53.188

八, Q&A

上一篇問題:在java中通常使用synchronized來實現方法同步,AQS中通過CAS保證了修改同步狀態的一致性問題,那麼對比synchronized,cas有什麼優勢不同與優勢呢?你還知道其他無鎖併發的策略嗎?

8.1 Answer

Java中的無鎖併發策略可以分為三種:

  1. 基於樂觀鎖的CAS操作
  2. Copy On Write:寫時複製是指:在併發訪問的情景下,當需要修改JAVA中Containers的元素時,不直接修改該容器,而是先複製一份副本,在副本上進行修改。修改完成之後,將指向原來容器的引用指向新的容器(副本容器)
  3. ThreadLocal:執行緒本地儲存,就是為每一個執行緒建立一個變數,只有本執行緒可以在該變數中檢視和修改值。

8.2 Question

  1. 這是一道送分題:正如上文提到的,Java8之前的日期時間以及格式化類是執行緒不安全的,你知道怎麼編寫測試程式碼嗎?

  2. 如果優雅得獲取昨天0點整的毫秒值?

學習Java過程中可能遇到問題和困惑,關注我vx公眾號 “cruder” ,後臺留言,筆者幫你一起解決!(需要學習資料的請關注後後臺留言,主要都是java相關,java基礎,併發,mysql,redis,es,mq等都都有!)