《Java8實戰》-第十二章筆記(新的日期和時間API)

雷俠發表於2019-03-01

新的日期和時間API

Java的API提供了很多有用的元件,能幫助你構建複雜的應用。不過,Java API也不總是完美的。我們相信大多數有經驗的程式設計師都會贊同Java 8之前的庫對日期和時間的支援就非常不理想。然而,你也不用太擔心:Java 8中引入全新的日期和時間API就是要解決這一問題。

在Java 1.0中,對日期和時間的支援只能依賴java.util.Date類。正如類名所表達的,這個類無法表示日期,只能以毫秒的精度表示時間。更糟糕的是它的易用性,由於某些原因未知的設計決策,這個類的易用性被深深地損害了,比如:年份的起始選擇是1900年,月份的起始從0開始。這意味著,如果你想要用Date表示Java 8的釋出日期,即2014年3月18日,需要建立下面這樣的Date例項:

Date date = new Date(114, 2, 18);
複製程式碼

它的列印輸出效果為:

Tue Mar 18 00:00:00 CST 2014
複製程式碼

看起來不那麼直觀,不是嗎?此外,甚至Date類的toString方法返回的字串也容易誤導人。

隨著Java 1.0退出歷史舞臺,Date類的種種問題和限制幾乎一掃而光,但很明顯,這些歷史舊賬如果不犧牲前向相容性是無法解決的。所以,在Java 1.1中,Date類中的很多方法被廢棄了,取而代之的是java.util.Calendar類。很不幸,Calendar類也有類似的問題和設計缺陷,導致使用這些方法寫出的程式碼非常容易出錯。比如,月份依舊是從0開始計算(不過,至少Calendar類拿掉了由1900年開始計算年份這一設計)。更糟的是,同時存在Date和Calendar這兩個類,也增加了程式設計師的困惑。到底該使用哪一個類呢?此外,有的特性只在某一個類有提供,比如用於以語言無關方式格式化和解析日期或時間的DateFormat方法就只在Date類裡有。

DateFormat方法也有它自己的問題。比如,它不是執行緒安全的。這意味著兩個執行緒如果嘗試使用同一個formatter解析日期,你可能會得到無法預期的結果。

最後,Date和Calendar類都是可以變的。能把2014年3月18日修改成4月18日意味著什麼呢?這種設計會將你拖入維護的噩夢,接下來的一章,我們會討論函數語言程式設計,你在該章中會了解到更多的細節。

這一章中,我們會一起探索新的日期和時間API所提供的新特性。我們從最基本的用例入手,比如建立同時適合人與機器的日期和時間,逐漸轉入到日期和時間API更高階的一些應用,比如操縱、解析、列印輸出日期時間物件,使用不同的時區和年曆。

LocalDate、LocalTime、Instant、Duration 以及Period

讓我們從探索如何建立簡單的日期和時間間隔入手。java.time包中提供了很多新的類可以幫你解決問題,它們是LocalDate、LocalTime、Instant、Duration和Period。

使用LocalDate 和LocalTime

開始使用新的日期和時間API時,你最先碰到的可能是LocalDate類。該類的例項是一個不可變物件,它只提供了簡單的日期,並不含當天的時間資訊。另外,它也不附帶任何與時區相關的資訊。

你可以通過靜態工廠方法of建立一個LocalDate例項。LocalDate例項提供了多種方法來讀取常用的值,比如年份、月份、星期幾等,如下所示。

LocalDate localDate = LocalDate.of(2014, 3, 18);
int year = localDate.getYear();
Month month = localDate.getMonth();
int day = localDate.getDayOfMonth();
DayOfWeek dow = localDate.getDayOfWeek();
int len = localDate.lengthOfMonth();
boolean leap = localDate.isLeapYear();

System.out.println(String.format("year:%s
month:%s
day:%s
dow:%s
len:%s
leap:%s", year, month, day, dow, len, leap));
複製程式碼

列印結果:

year:2014
month:MARCH
day:18
dow:TUESDAY
len:31
leap:false
複製程式碼

你還可以使用工廠方法從系統時鐘中獲取當前的日期:

LocalDate today = LocalDate.now();
複製程式碼

接下來剩餘的部分會探討所有日期-時間類,這些類都提供了類似的工廠方法。你還可以通過傳遞一個TemporalField引數給get方法拿到同樣的資訊。TemporalField是一個介面,它定義瞭如何訪問temporal物件某個欄位的值。ChronoField列舉實現了這一介面,所以你可以很方便地使用get方法得到列舉元素的值,如下所示。

int year = localDate.get(ChronoField.YEAR);
int month = localDate.get(ChronoField.MONTH_OF_YEAR);
int day = localDate.get(ChronoField.DAY_OF_MONTH);
複製程式碼

類似地,一天中的時間,比如13:45:20,可以使用LocalTime類表示。你可以使用of過載的兩個工廠方法建立LocalTime的例項。第一個過載函式接收小時和分鐘,第二個過載函式同時還接收秒。同LocalDate一樣,LocalTime類也提供了一些getter方法訪問這些變數的值,如下所示。

LocalTime localTime = LocalTime.of(13, 45, 20);
int hour = localTime.getHour();
int minute = localTime.getMinute();
int second = localTime.getSecond();

System.out.println(String.format("hour:%s
minute:%s
second:%s", hour, minute, second));
複製程式碼

列印結果:

hour:13
minute:45
second:20
複製程式碼

LocalDate和LocalTime都可以通過解析代表它們的字串建立。使用靜態方法parse,你可以實現這一目的:

LocalDate date = LocalDate.parse("2018-11-17");
LocalTime time = LocalTime.parse("21:27:58");
複製程式碼

你可以向parse方法傳遞一個DateTimeFormatter。該類的例項定義瞭如何格式化一個日期或者時間物件。正如我們之前所介紹的,它是替換老版java.util.DateFormat的推薦替代品。這個我們後面將會討論到。同時,也請注意,一旦傳遞的字串引數無法被解析為合法的LocalDate或LocalTime物件,這兩個parse方法都會丟擲一個繼承自RuntimeException的DateTimeParseException異常。

合併日期和時間

這個複合類名叫LocalDateTime,是LocalDate和LocalTime的合體。它同時表示了日期和時間,但不帶有時區資訊,你可以直接建立,也可以通過合併日期和時間物件構造,如下所示。

// 2018-11-17T21:31:50
LocalTime time = LocalTime.of(21, 31, 50);
LocalDate date = LocalDate.of(2018, 11, 17);

LocalDateTime dt1 = LocalDateTime.of(2018, Month.NOVEMBER, 17, 21, 31, 50);
LocalDateTime dt2 = LocalDateTime.of(date, time);
LocalDateTime dt3 = date.atTime(21, 11, 17);
LocalDateTime dt4 = date.atTime(time);
LocalDateTime dt5 = time.atDate(date);
複製程式碼

注意,通過它們各自的atTime或者atDate方法,向LocalDate傳遞一個時間物件,或者向LocalTime傳遞一個日期物件的方式,你可以建立一個LocalDateTime物件。你也可以使用toLocalDate或者toLocalTime方法,從LocalDateTime中提取LocalDate或者LocalTime元件:

LocalDate date1 = dt1.toLocalDate();
LocalTime time1 = dt1.toLocalTime();
複製程式碼

機器的日期和時間格式

作為人,我們習慣於以星期幾、幾號、幾點、幾分這樣的方式理解日期和時間。毫無疑問,這種方式對於計算機而言並不容易理解。從計算機的角度來看,建模時間最自然的格式是表示一個持續時間段上某個點的單一大整型數。這也是新的java.time.Instant類對時間建模的方式,基本上它是以Unix元年時間(傳統的設定為UTC時區1970年1月1日午夜時分)開始所經歷的秒數進行計算。

你可以通過向靜態工廠方法ofEpochSecond傳遞一個代表秒數的值建立一個該類的例項。靜態工廠方法ofEpochSecond還有一個增強的過載版本,它接收第二個以納秒為單位的引數值,對傳入作為秒數的引數進行調整。過載的版本會調整納秒引數,確保儲存的納秒分片在0到999 999999之間。這意味著下面這些對ofEpochSecond工廠方法的呼叫會返回幾乎同樣的Instant物件:

Instant.ofEpochSecond(3);
Instant.ofEpochSecond(3, 0);
// 2 秒之後再加上100萬納秒(1秒)
Instant.ofEpochSecond(2, 1_000_000_000);
// 4秒之前的100萬納秒(1秒)
Instant.ofEpochSecond(4, -1_000_000_000);
複製程式碼

正如你已經在LocalDate及其他為便於閱讀而設計的日期-時間類中所看到的那樣,Instant類也支援靜態工廠方法now,它能夠幫你獲取當前時刻的時間戳。我們想要特別強調一點,Instant的設計初衷是為了便於機器使用。它包含的是由秒及納秒所構成的數字。所以,它無法處理那些我們非常容易理解的時間單位。比如下面這段語句:

int day = Instant.now().get(ChronoField.DAY_OF_MONTH);
複製程式碼

它會丟擲下面這樣的異常:

Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: DayOfMonth
複製程式碼

但是你可以通過Duration和Period類使用Instant,接下來我們會對這部分內容進行介紹。

定義Duration 或Period

目前為止,你看到的所有類都實現了Temporal介面,Temporal介面定義瞭如何讀取和操縱為時間建模的物件的值。之前的介紹中,我們已經瞭解了建立Temporal例項的幾種方法。很自然地你會想到,我們需要建立兩個Temporal物件之間的duration。Duration類的靜態工廠方法between就是為這個目的而設計的。你可以建立兩個LocalTimes物件、兩個LocalDateTimes物件,或者兩個Instant物件之間的duration,如下所示:

LocalTime time1 = LocalTime.of(21, 50, 10);
LocalTime time2 = LocalTime.of(22, 50, 10);
LocalDateTime dateTime1 = LocalDateTime.of(2018, 11, 17, 21, 50, 10);
LocalDateTime dateTime2 = LocalDateTime.of(2018, 11, 17, 23, 50, 10);
Instant instant1 = Instant.ofEpochSecond(1000 * 60 * 2);
Instant instant2 = Instant.ofEpochSecond(1000 * 60 * 3);

Duration d1 = Duration.between(time1, time2);
Duration d2 = Duration.between(dateTime1, dateTime2);
Duration d3 = Duration.between(instant1, instant2);
// PT1H 相差1小時
System.out.println("d1:" + d1);
// PT2H 相差2小時
System.out.println("d2:" + d2);
// PT16H40M 相差16小時40分鐘
System.out.println("d3:" + d3);
複製程式碼

由於LocalDateTime和Instant是為不同的目的而設計的,一個是為了便於人閱讀使用,另一個是為了便於機器處理,所以你不能將二者混用。如果你試圖在這兩類物件之間建立duration,會觸發一個DateTimeException異常。此外,由於Duration類主要用於以秒和納秒衡量時間的長短,你不能僅向between方法傳遞一個LocalDate物件做引數。

如果你需要以年、月或者日的方式對多個時間單位建模,可以使用Period類。使用該類的工廠方法between,你可以使用得到兩個LocalDate之間的時長,如下所示:

Period period = Period.between(LocalDate.of(2018, 11, 7), LocalDate.of(2018, 11, 17));
// P10D 相差10天
System.out.println("Period between:" + period);
複製程式碼

最後,Duration和Period類都提供了很多非常方便的工廠類,直接建立對應的例項;換句話說,就像下面這段程式碼那樣,不再是隻能以兩個temporal物件的差值的方式來定義它們的物件。

Duration threeMinutes = Duration.ofMinutes(3);
Duration fourMinutes = Duration.of(4, ChronoUnit.MINUTES);

Period tenDay = Period.ofDays(10);
Period threeWeeks = Period.ofWeeks(3);
Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);
複製程式碼

Duration類和Period類共享了很多相似的方法,有興趣的可以參考官網的文件。

截至目前,我們介紹的這些日期時間物件都是不可修改的,這是為了更好地支援函數語言程式設計,確保執行緒安全,保持領域模式一致性而做出的重大設計決定。當然,新的日期和時間API也提供了一些便利的方法來建立這些物件的可變版本。比如,你可能希望在已有的LocalDate例項上增加3天。除此之外,我們還會介紹如何依據指定的模式,比如dd/MM/yyyy,建立日期-時間格式器,以及如何使用這種格式器解析和輸出日期。

操縱、解析和格式化日期

如果你已經有一個LocalDate物件,想要建立它的一個修改版,最直接也最簡單的方法是使用withAttribute方法。withAttribute方法會建立物件的一個副本,並按照需要修改它的屬性。注意,下面的這段程式碼中所有的方法都返回一個修改了屬性的物件。它們都不會修改原來的物件!

// 2018-11-17
LocalDate date1 = LocalDate.of(2018, 11, 17);
// 2019-11-17
LocalDate date2 = date1.withYear(2019);
// 2019-11-25
LocalDate date3 = date2.withDayOfMonth(25);
// 2019-09-25
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9);
複製程式碼

它們都宣告於Temporal介面,所有的日期和時間API類都實現這兩個方法,它們定義了單點的時間,比如LocalDate、LocalTime、LocalDateTime以及Instant。更確切地說,使用get和with方法,我們可以將Temporal物件值的讀取和修改區分開。如果Temporal物件不支援請求訪問的欄位,它會丟擲一個UnsupportedTemporalTypeException異常,比如試圖訪問Instant物件的ChronoField.MONTH_OF_YEAR欄位,或者LocalDate物件的ChronoField.NANO_OF_SECOND欄位時都會丟擲這樣的異常。

它甚至能以宣告的方式操縱LocalDate物件。比如,你可以像下面這段程式碼那樣加上或者減去一段時間。

// 2018-11-17
LocalDate date1 = LocalDate.of(2018, 11, 17);
// 2018-11-24
LocalDate date2 = date1.plusWeeks(1);
// 2015-11-24
LocalDate date3 = date2.minusYears(3);
// 2016-05-24
LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS);
複製程式碼

與我們剛才介紹的get和with方法類似最後一行使用的plus方法也是通用方法,它和minus方法都宣告於Temporal介面中。通過這些方法,對TemporalUnit物件加上或者減去一個數字,我們能非常方便地將Temporal物件前溯或者回滾至某個時間段,通過ChronoUnit列舉我們可以非常方便地實現TemporalUnit介面。

大概你已經猜到,像LocalDate、LocalTime、LocalDateTime以及Instant這樣表示時
間點的日期時間類提供了大量通用的方法,我們目前所使用的只有一小部分,有興趣的可以去看官網文件。

使用TemporalAdjuster

截至目前,你所看到的所有日期操作都是相對比較直接的。有的時候,你需要進行一些更加複雜的操作,比如,將日期調整到下個週日、下個工作日,或者是本月的最後一天。這時,你可以使用過載版本的with方法,向其傳遞一個提供了更多定製化選擇的TemporalAdjuster物件,更加靈活地處理日期。對於最常見的用例, 日期和時間API已經提供了大量預定義的TemporalAdjuster。你可以通過TemporalAdjuster類的靜態工廠方法訪問它們,如下所示。

// 2018-11-17
LocalDate date1 = LocalDate.of(2018, 11, 17);
// 2018-11-19
LocalDate date2 = date1.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY));
// 2018-11-30
LocalDate date3 = date2.with(TemporalAdjusters.lastDayOfMonth());
複製程式碼

正如我們看到的,使用TemporalAdjuster我們可以進行更加複雜的日期操作,而且這些方法的名稱也非常直觀,方法名基本就是問題陳述。此外,即使你沒有找到符合你要求的預定義的TemporalAdjuster,建立你自己的TemporalAdjuster也並非難事。實際上,TemporalAdjuster介面只宣告瞭單一的一個方法(這使得它成為了一個函式式介面),定義如下。

@FunctionalInterface
public interface TemporalAdjuster {
    Temporal adjustInto(Temporal temporal);
}
複製程式碼

這意味著TemporalAdjuster介面的實現需要定義如何將一個Temporal物件轉換為另一個Temporal物件。你可以把它看成一個UnaryOperator。

你可能希望對你的日期時間物件進行的另外一個通用操作是,依據你的業務領域以不同的格式列印輸出這些日期和時間物件。類似地,你可能也需要將那些格式的字串轉換為實際的日期物件。接下來的一節,我們會演示新的日期和時間API提供那些機制是如何完成這些任務的。

列印輸出及解析日期-時間物件

處理日期和時間物件時,格式化以及解析日期時間物件是另一個非常重要的功能。新的java.time.format包就是特別為這個目的而設計的。這個包中,最重要的類是DateTimeFormatter。建立格式器最簡單的方法是通過它的靜態工廠方法以及常量。像BASIC_ISO_DATE和ISO_LOCAL_DATE 這樣的常量是DateTimeFormatter 類的預定義例項。所有的DateTimeFormatter例項都能用於以一定的格式建立代表特定日期或時間的字串。比如,下面的這個例子中,我們使用了兩個不同的格式器生成了字串:

LocalDate date1 = LocalDate.of(2018, 11, 17);
// 20181117
String s1 = date1.format(DateTimeFormatter.BASIC_ISO_DATE);
// 2018-11-17
String s2 = date1.format(DateTimeFormatter.ISO_LOCAL_DATE);
複製程式碼

你也可以通過解析代表日期或時間的字串重新建立該日期物件。所有的日期和時間API都提供了表示時間點或者時間段的工廠方法,你可以使用工廠方法parse達到重創該日期物件的目的:

LocalDate date2 = LocalDate.parse("20181117", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date3 = LocalDate.parse("2018-11-17", DateTimeFormatter.ISO_LOCAL_DATE);
複製程式碼

和老的java.util.DateFormat相比較,所有的DateTimeFormatter例項都是執行緒安全的。所以,你能夠以單例模式建立格式器例項,就像DateTimeFormatter所定義的那些常量,並能在多個執行緒間共享這些例項。DateTimeFormatter類還支援一個靜態工廠方法,它可以按照某個特定的模式建立格式器,程式碼清單如下。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
// 17/11/2018
String formattedDate = date1.format(formatter);
LocalDate date4 = LocalDate.parse(formattedDate, formatter);
複製程式碼

這段程式碼中,LocalDate的formate方法使用指定的模式生成了一個代表該日期的字串。緊接著,靜態的parse方法使用同樣的格式器解析了剛才生成的字串,並重建了該日期物件。ofPattern方法也提供了一個過載的版本,使用它你可以建立某個Locale的格式器,程式碼清單如下所示。

 DateTimeFormatter italianFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.ITALIAN);
LocalDate date5 = LocalDate.of(2018, 11, 16);
// 16. novembre 2018
String formattedDate2 = date5.format(italianFormatter);
// 2018-11-16
LocalDate date6 = LocalDate.parse(formattedDate2, italianFormatter);
複製程式碼

最後,如果你還需要更加細粒度的控制,DateTimeFormatterBuilder類還提供了更復雜的格式器,你可以選擇恰當的方法,一步一步地構造自己的格式器。另外,它還提供了非常強大的解析功能,比如區分大小寫的解析、柔性解析(允許解析器使用啟發式的機制去解析輸入,不精確地匹配指定的模式)、填充, 以及在格式器中指定可選節。

比如, 你可以通過DateTimeFormatterBuilder自己程式設計實現我們在上面程式碼中使用的italianFormatter,程式碼清單如下。

DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder()
                .appendText(ChronoField.DAY_OF_MONTH)
                .appendLiteral(". ")
                .appendText(ChronoField.MONTH_OF_YEAR)
                .appendLiteral(" ")
                .appendText(ChronoField.YEAR)
                .parseCaseInsensitive()
                .toFormatter(Locale.ITALIAN);

LocalDate now = LocalDate.now();
// 17. novembre 2018
String s1 = now.format(italianFormatter);
複製程式碼

目前為止,你已經學習瞭如何建立、操縱、格式化以及解析時間點和時間段,但是你還不瞭解如何處理日期和時間之間的微妙關係。比如,你可能需要處理不同的時區,或者由於不同的歷法系統帶來的差異。接下來的一節,我們會探究如何使用新的日期和時間API解決這些問題。

處理不同的時區和曆法

之前你看到的日期和時間的種類都不包含時區資訊。時區的處理是新版日期和時間API新增加的重要功能,使用新版日期和時間API時區的處理被極大地簡化了。新的java.time.ZoneId類是老版java.util.TimeZone的替代品。它的設計目標就是要讓你無需為時區處理的複雜和繁瑣而操心,比如處理日光時(Daylight Saving Time,DST)這種問題。跟其他日期和時間類一樣,ZoneId類也是無法修改的。

時區是按照一定的規則將區域劃分成的標準時間相同的區間。在ZoneRules這個類中包含了40個這樣的例項。你可以簡單地通過呼叫ZoneId的getRules()得到指定時區的規則。每個特定的ZoneId物件都由一個地區ID標識,比如:

ZoneId shanghaiZone = ZoneId.of("Asia/Shanghai");
複製程式碼

地區ID都為“{區域}/{城市}”的格式,這些地區集合的設定都由英特網編號分配機構(IANA)的時區資料庫提供。你可以通過Java 8的新方法toZoneId將一個老的時區物件轉換為ZoneId:

ZoneId zoneId = TimeZone.getDefault().toZoneId();
複製程式碼

一旦得到一個ZoneId物件,你就可以將它與LocalDate、LocalDateTime或者是Instant物件整合起來,構造為一個ZonedDateTime例項,它代表了相對於指定時區的時間點,程式碼清單如下所示。

LocalDate date = LocalDate.of(2018, 11, 17);
ZonedDateTime zdt1 = date.atStartOfDay(shanghaiZone);

LocalDateTime dateTime = LocalDateTime.of(2018, 11, 27, 18, 13, 15);
ZonedDateTime zdt2 = dateTime.atZone(shanghaiZone);

Instant instant = Instant.now();
ZonedDateTime zdt3 = instant.atZone(shanghaiZone);
複製程式碼

通過ZoneId,你還可以將LocalDateTime轉換為Instant:

LocalDateTime dateTime = LocalDateTime.of(2018, 11, 17, 18, 45);
Instant instantFromDateTime = dateTime.toInstant(shanghaiZone);
複製程式碼

你也可以通過反向的方式得到LocalDateTime物件:

Instant instant = Instant.now();
LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant, shanghaiZone);
複製程式碼

利用和UTC/格林尼治時間的固定偏差計算時區

另一種比較通用的表達時區的方式是利用當前時區和UTC/格林尼治的固定偏差。比如,基於這個理論,你可以說“紐約落後於倫敦5小時”。這種情況下,你可以使用ZoneOffset類,它是ZoneId的一個子類,表示的是當前時間和倫敦格林尼治子午線時間的差異:

ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");
複製程式碼

“-05:00”的偏差實際上對應的是美國東部標準時間。注意,使用這種方式定義的ZoneOffset並未考慮任何日光時的影響,所以在大多數情況下,不推薦使用。由於ZoneOffset也是ZoneId,所以你可以像上面的程式碼那樣使用它。你甚至還可以建立這樣的OffsetDateTime,它使用ISO-8601的歷法系統,以相對於UTC/格林尼治時間的偏差方式表示日期時間。

LocalDateTime dateTime = LocalDateTime.of(2018, 11, 17, 18, 45);
OffsetDateTime offsetDateTime = OffsetDateTime.of(dateTime, newYorkOffset);
複製程式碼

總結

  • Java 8之前老版的java.util.Date類以及其他用於建模日期時間的類有很多不一致及設計上的缺陷,包括易變性以及糟糕的偏移值、預設值和命名。
  • 新版的日期和時間API中,日期-時間物件是不可變的。
  • 新的API提供了兩種不同的時間表示方式,有效地區分了執行時人和機器的不同需求。
  • 你可以用絕對或者相對的方式操縱日期和時間,操作的結果總是返回一個新的例項,老的日期時間物件不會發生變化。
  • TemporalAdjuster讓你能夠用更精細的方式操縱日期,不再侷限於一次只能改變它的一個值,並且你還可按照需求定義自己的日期轉換器。
  • 你現在可以按照特定的格式需求,定義自己的格式器,列印輸出或者解析日期時間物件。這些格式器可以通過模板建立,也可以自己程式設計建立,並且它們都是執行緒安全的。
  • 你可以用相對於某個地區/位置的方式,或者以與UTC/格林尼治時間的絕對偏差的方式表示時區,並將其應用到日期時間物件上,對其進行本地化。

可以說《Java8實戰》的讀書筆記相關的已經寫完了,這本書後面還有最後一部分超越Java8,這一部分相關的章節都是跟函數語言程式設計的思考與技巧相關,以及Java以後的未來等等。《Java8實戰》這本書真的寫的太好了而且這本書完全可以當作一本關於Java8使用的工具書,隨時可以翻開看看,看看關於Java8的特性是如何使用,該如何去避免一些坑,該如何使用Stream和Lambda表示式去簡化你的程式碼。

程式碼

Gitee:chap12

Github:chap12

相關文章