该文章Github地址:https://github.com/AntonyCheng/java-notes【有条件的情况下推荐直接访问GitHub以获取最新的代码更新】
在此介绍一下作者开源的SpringBoot项目初始化模板(Github仓库地址:https://github.com/AntonyCheng/spring-boot-init-template【有条件的情况下推荐直接访问GitHub以获取最新的代码更新】& CSDN文章地址:https://blog.csdn.net/AntonyCheng/article/details/136555245),该模板集成了最常见的开发组件,同时基于修改配置文件实现组件的装载,除了这些,模板中还有非常丰富的整合示例,同时单体架构也非常适合SpringBoot框架入门,如果觉得有意义或者有帮助,欢迎Star & Issues & PR!
上一章:由浅到深认识Java语言(49):JDK8新特性
53.JDK8新特性
前言
到此为止,Java 的基础知识都已经全部展现出来了,但是随着 Java 的更新迭代,Java 不同版本之间会出现一些显著特性,之前有关 Java 基础知识的所有文章均是基于 JDK8 版本所描述的,此后的文章就会推出 JDK8 之后版本的特性介绍。
本章节是“JDK8新特性“,但有一些已经在以往的文章中做出过介绍,它们的文章地址如下:
- 由浅到深认识Java语言(45):Lambda表达式
- 由浅到深认识Java语言(46):Lambda表达式
- 由浅到深认识Java语言(47):Stream流
- 由浅到深认识Java语言(48):Optional类
接口默认方法
JDK8之前的接口
在 JDK8 之前,interface 之中可以定义变量和方法,变量必须是 public 、static 、final 的,方法必须是 public 、abstract 的。由于这些修饰符都是默认的以下写法等价:
public interface JDK8BeforeInterface {
public static final int field1 = 0;
int field2 = 0;
public abstract void method1(int a) throws Exception;
void method2(int a) throws Exception;
}
JDK8之后的接口
JDK8及以后,允许我们在接口中定义static方法和default方法
- 接口中定义的静态static方法只能通过接口名直接调用,default的方法需要用接口的实现类的对象来调用
- 接口中的static和default方法可以有函数体,其实现类不必要重写
- 其他的非static和非default的都是抽象方法,没有函数体,其实现类必须重写所有的抽象方法
- 如果子类(或实现类)继承的父类和其实现的接口定义了同名同参的方法,并且接口中的方法为default方法(都有函数体),那么该子类的对象调用该方法时(在子类没有重写该方法的情况下),默认是父类的方法(类优先性)
- 如果类实现了多个接口,而且多个接口中定义了同名同参数的default方法(有函数体),在该类没有重写的情况下,就会报错(接口冲突)。如果想解决这个问题,就必须在该类中重写此方法。
public interface JDK8Interface {
// static修饰符定义静态方法
static void staticMethod() {
System.out.println("接口中的静态方法");
}
// default修饰符定义默认方法
default void defaultMethod() {
System.out.println("接口中的默认方法");
}
}
public class JDK8InterfaceImpl implements JDK8Interface {
//实现接口后,因为默认方法不是抽象方法,所以可以不重写,但是如果开发需要,也可以重写
}
public class Main {
public static void main(String[] args) {
// static方法必须通过接口类调用
JDK8Interface.staticMethod();
//default方法必须通过实现类的对象调用
new JDK8InterfaceImpl().defaultMethod();
}
}
public class AnotherJDK8InterfaceImpl implements JDK8Interface {
// 当然如果接口中的默认方法不能满足某个实现类需要,那么实现类可以覆盖默认方法。
// 签名跟接口default方法一致,但是不能再加default修饰符
@Override
public void defaultMethod() {
System.out.println("接口实现类覆盖了接口中的default");
}
}
Nashorn JavaScript
对于 Java 中的 JavaScript 引擎, Java 8 引入了 Nashorn 来代替原先的 Rhino。
Nashorn 使用 Java 7 中引入的调用动态特性,且直接编译内存中的代码并将字节码传递给 JVM。这两项改进,直接给 Nashorn 带了至少 2 到 10 倍的性能提升。
在 Nashorn JavaScript 引擎中。Java 8 引入了一个新的命令行工具 jjs
,用于在控制台执行 JavaScript 代码。例如可以在当前目录下 ( 任意位置 ) 创建一个 JavaScript 文件 hello.js ,然后在命令行中使用如下命令就可以运行 hello.js 文件。
日期时间API
从 Java 5 开始,处理日期时间都由 java.util.Date
、java.util.Calendar
、java.util.GregoiranCalendar
和 java.text.SimpleDateFormat
四大类负责,分别用于处理日期、日历、日历表示、日期时间格式化。
但是这些类早在很久以前就有非常多设计不合理的地方,比如 java.util.Date
并非线程安全; java.util.Date
默认的时间年月日分别从 1900,1,0 开始的,非常反人类…于是乎在 Java 8 中重新设计了所有时间、日历和时区相关的API,并且统一规划在了 java.time
包及其子包下。
本地日期时间
Java 8 为处理本地的日期时间提供了三个类 LocalDate
、LocalTime
和 LocalDateTime
。分别用于处理 本地日期、本地时间 和 本地日期时间。
当使用这三个类时,开发者并不需要关心时区是什么。因为它默认使用的是操作系统的时区。
比如,可以使用 LocalDateTime.now()
方法返回当前的日期时间。
获取当前日期时间示例如下:
public static void main(String[] args) {
System.out.println("LocalDate.now() = " + LocalDate.now());
System.out.println("LocalTime.now() = " + LocalTime.now());
System.out.println("LocalDateTime.now() = " + LocalDateTime.now());
}
打印效果如下:
LocalDate.now() = 2024-03-31
LocalTime.now() = 16:49:37.469327600
LocalDateTime.now() = 2024-03-31T16:49:37.469327600
获取当前时间的年月日时分秒示例如下:
public static void main(String[] args) {
LocalDateTime dateTime = LocalDateTime.now();
System.out.println("dateTime = " + dateTime);
System.out.println("dateTime.getYear() = " + dateTime.getYear());
System.out.println("dateTime.getMonth() = " + dateTime.getMonth());
System.out.println("dateTime.getDayOfMonth() = " + dateTime.getDayOfMonth());
System.out.println("dateTime.getHour() = " + dateTime.getHour());
System.out.println("dateTime.getMinute() = " + dateTime.getMinute());
System.out.println("dateTime.getSecond() = " + dateTime.getSecond());
}
打印结果如下:
dateTime = 2024-03-31T16:54:24.719364900
dateTime.getYear() = 2024
dateTime.getMonth() = MARCH
dateTime.getDayOfMonth() = 31
dateTime.getHour() = 16
dateTime.getMinute() = 54
dateTime.getSecond() = 24
创建日期时间示例如下:
public static void main(String[] args) {
LocalDateTime customDateTime = LocalDateTime.of(2001, 11, 11, 12, 13, 14);
System.out.println("customDateTime = " + customDateTime);
}
打印结果如下:
customDateTime = 2001-11-11T12:13:14
修改日期时间示例如下:
public static void main(String[] args) {
LocalDateTime oldDateTime = LocalDateTime.of(2001, 11, 11, 12, 13, 14);
LocalDateTime newDateTime = oldDateTime
.withYear(2020)
.withMonth(1)
.withDayOfMonth(1)
.withMinute(0)
.withSecond(0);
System.out.println("oldDateTime = " + oldDateTime);
System.out.println("newDateTime = " + newDateTime);
}
打印结果如下:
oldDateTime = 2001-11-11T12:13:14
newDateTime = 2020-01-01T12:00
解析字符串示例如下:
public static void main(String[] args) {
LocalDateTime parseDateTime = LocalDateTime.parse("2012-10-10T21:58:00");
LocalDateTime parseFormatDateTime = LocalDateTime.parse("2020.01.01 12:13:14", DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss"));
System.out.println("parseDateTime = " + parseDateTime);
System.out.println("parseFormatDateTime = " + parseFormatDateTime);
}
打印效果如下:
parseDateTime = 2012-10-10T21:58
parseFormatDateTime = 2020-01-01T12:13:14
时区日期时间
LocalDateTime
类中看似没有时区的相关信息,但是它们内部其实已经处理了时区,只是默认使用的是当前操作系统的时区。但是如果自己维护的服务器位于德国柏林,上面的 Java 程序为中国境内用户提供服务,此时就需要从代码层面约定时区,Java 8 中 java.time.ZonedDateTime
和 java.time.ZoneId
就起到了作用,前者用于处理需要时区的日期时间,后者用于处理时区。
ZonedDateTime
和 LocalDateTime
类似,几乎有着相同的 API。从某些方面说,ZonedLocalTime
如果不传递时区信息,那么它会默认使用操作系统的时区,这样,结果其实和 LocalDateTime
是类似的,而且在时区确定的情况下,这两个类是可以互相转换的,示例如下:
public static void main(String[] args) {
ZonedDateTime zonedDateTime = ZonedDateTime.now();
LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();
System.out.println("zonedDateTime = " + zonedDateTime);
System.out.println("localDateTime = " + localDateTime);
}
打印效果如下:
zonedDateTime = 2024-03-31T17:11:44.140144800+08:00[Asia/Shanghai]
localDateTime = 2024-03-31T17:11:44.140144800
其实如果查看源码,now()
方法底层调用的是 now(Clock.systemDefaultZone())
,而 Clock.systemDefaultZone()
方法返回的就是一个 new SystemClock(ZoneId.systemDefault())
,所以这里可以得出获取本地电脑时区的方法:
public static void main(String[] args) {
System.out.println("ZoneId.systemDefault() = " + ZoneId.systemDefault());
}
打印效果如下:
ZoneId.systemDefault() = Asia/Shanghai
其实也可以直接通过没有定义时区的 ZonedDateTime
对象获取本地电脑时区:
public static void main(String[] args) {
System.out.println("ZonedDateTime.now().getZone() = " + ZonedDateTime.now().getZone());
}
打印效果如下:
ZonedDateTime.now().getZone() = Asia/Shanghai
在定义时区之前需要知道有哪些时区可以被我们使用,使用 ZoneId.getAvailableZoneIds()
方法即可查看,这里我们定义一个时区为德国柏林的2001年11月11日12时13分14秒:
public static void main(String[] args) {
ZonedDateTime zonedDateTime = ZonedDateTime.of(2001, 11, 11, 12, 13, 14, 0, ZoneId.of("Europe/Berlin"));
System.out.println("zonedDateTime = " + zonedDateTime);
}
打印效果如下:
zonedDateTime = 2001-11-11T12:13:14+01:00[Europe/Berlin]
格式化
Java 8 似乎对 java.text.SimpleDateFormat
也不太满意,重新创建了一个 java.time.format
包,该包下包含了格式化的类和枚举。
java.time.format包
java.time.format
包提供了以下几个类用于格式化日期时间:
类 | 说明 |
---|---|
DateTimeFormatter | 用于打印和解析日期时间对象的格式化程序 |
DateTimeFormatterBuilder | 创建日期时间格式化样式的构建器 |
DecimalStyle | 日期和时间格式中使用的本地化十进制样式 |
java.time.format
包还提供了以下几个枚举,包含了常见的几种日期时间格式:
枚举 | 说明 |
---|---|
FormatStyle | 包含了本地化日期,时间或日期时间格式器的样式的枚举 |
ResolverStyle | 包含了解决日期和时间的不同方法的枚举 |
SignStyle | 包含了如何处理正/负号的方法的枚举 |
TextStyle | 包含了文本格式和解析的样式的枚举 |
DateTimeFormatter类
DateTimeFormatter 类格式化日期时间的最重要的类,用于打印和解析日期时间对象的格式化器,而且该类是一个最终类,只能实例化,不能被扩展和继承。
所有日期时间类都提供了两个重要的方法:
- 一个用于格式化
format(DateTimeFormatterformatter)
,将日期时间类转换成字符串; - 另一个用于解析
parse(CharSequencetext,DateTimeFormatterformatter)
,将字符串转换为日期时间类。
DateTimeFormatter类自带有一些格式化格式,这些格式化格式在 ZonedDateTime 类使用时全部适配,而部分格式在 LocalDateTime 类下会不兼容,示例如下:
public static void main(String[] args) {
ZonedDateTime dateTime = ZonedDateTime.now();
System.out.println("dateTime.format(DateTimeFormatter.BASIC_ISO_DATE) = " + dateTime.format(DateTimeFormatter.BASIC_ISO_DATE));
System.out.println("dateTime.format(DateTimeFormatter.ISO_DATE) = " + dateTime.format(DateTimeFormatter.ISO_DATE));
System.out.println("dateTime.format(DateTimeFormatter.ISO_DATE_TIME) = " + dateTime.format(DateTimeFormatter.ISO_DATE_TIME));
System.out.println("dateTime.format(DateTimeFormatter.ISO_INSTANT) = " + dateTime.format(DateTimeFormatter.ISO_INSTANT));
System.out.println("dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE) = " + dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE));
System.out.println("dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) = " + dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
System.out.println("dateTime.format(DateTimeFormatter.ISO_LOCAL_TIME) = " + dateTime.format(DateTimeFormatter.ISO_LOCAL_TIME));
System.out.println("dateTime.format(DateTimeFormatter.ISO_OFFSET_DATE) = " + dateTime.format(DateTimeFormatter.ISO_OFFSET_DATE));
System.out.println("dateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) = " + dateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
System.out.println("dateTime.format(DateTimeFormatter.ISO_OFFSET_TIME) = " + dateTime.format(DateTimeFormatter.ISO_OFFSET_TIME));
System.out.println("dateTime.format(DateTimeFormatter.ISO_ORDINAL_DATE) = " + dateTime.format(DateTimeFormatter.ISO_ORDINAL_DATE));
System.out.println("dateTime.format(DateTimeFormatter.ISO_TIME) = " + dateTime.format(DateTimeFormatter.ISO_TIME));
System.out.println("dateTime.format(DateTimeFormatter.ISO_WEEK_DATE) = " + dateTime.format(DateTimeFormatter.ISO_WEEK_DATE));
System.out.println("dateTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME) = " + dateTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
System.out.println("dateTime.format(DateTimeFormatter.RFC_1123_DATE_TIME) = " + dateTime.format(DateTimeFormatter.RFC_1123_DATE_TIME));
}
打印效果如下:
dateTime.format(DateTimeFormatter.BASIC_ISO_DATE) = 20240331+0800
dateTime.format(DateTimeFormatter.ISO_DATE) = 2024-03-31+08:00
dateTime.format(DateTimeFormatter.ISO_DATE_TIME) = 2024-03-31T17:50:14.8944319+08:00[Asia/Shanghai]
dateTime.format(DateTimeFormatter.ISO_INSTANT) = 2024-03-31T09:50:14.894431900Z
dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE) = 2024-03-31
dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) = 2024-03-31T17:50:14.8944319
dateTime.format(DateTimeFormatter.ISO_LOCAL_TIME) = 17:50:14.8944319
dateTime.format(DateTimeFormatter.ISO_OFFSET_DATE) = 2024-03-31+08:00
dateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) = 2024-03-31T17:50:14.8944319+08:00
dateTime.format(DateTimeFormatter.ISO_OFFSET_TIME) = 17:50:14.8944319+08:00
dateTime.format(DateTimeFormatter.ISO_ORDINAL_DATE) = 2024-091+08:00
dateTime.format(DateTimeFormatter.ISO_TIME) = 17:50:14.8944319+08:00
dateTime.format(DateTimeFormatter.ISO_WEEK_DATE) = 2024-W13-7+08:00
dateTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME) = 2024-03-31T17:50:14.8944319+08:00[Asia/Shanghai]
dateTime.format(DateTimeFormatter.RFC_1123_DATE_TIME) = Sun, 31 Mar 2024 17:50:14 +0800
除了上面自带的格式,还允许按照一定规则自定义格式,示例如下:
public static void main(String[] args) {
ZonedDateTime nowDateTime = ZonedDateTime.now();
System.out.println("nowDateTime = " + nowDateTime);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
String formatterDateTime = nowDateTime.format(formatter);
System.out.println("formatterDateTime = " + formatterDateTime);
}
打印效果如下:
nowDateTime = 2024-03-31T17:58:17.194510800+08:00[Asia/Shanghai]
formatterDateTime = 2024/03/31 17:58:17
当然这里可以使用刚刚提到的 parse()
方法将日期时间从字符串解析回来,但要注意自定义格式中没有时区信息,所以不能使用 ZonedDateTime 类去解析,而要使用 LocalDateTime 类,示例如下:
public static void main(String[] args) {
ZonedDateTime nowDateTime = ZonedDateTime.now();
System.out.println("nowDateTime = " + nowDateTime);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
String formatterDateTime = nowDateTime.format(formatter);
System.out.println("formatterDateTime = " + formatterDateTime);
LocalDateTime parseDateTime = LocalDateTime.parse(formatterDateTime, formatter);
System.out.println("parseDateTime = " + parseDateTime);
}
打印效果如下:
nowDateTime = 2024-03-31T18:02:24.353967+08:00[Asia/Shanghai]
formatterDateTime = 2024/03/31 18:02:24
parseDateTime = 2024-03-31T18:02:24