新的日期和时间 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\nmonth:%s\nday:%s\ndow:%s\nlen:%s\nleap:%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\nminute:%s\nsecond:%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
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于