Java 8 新特性二
形而上 Lv4

学习必须往深处挖,挖的越深,基础越扎实!

Java 现在发布的版本很快,每年两个,但是真正会被大规模使用的是 3 年一个的 LTS 版本。

每 3 年发布一个 LTS(Long-Term Support),长期维护版本。意味着只有Java 8 ,Java 11, Java 17,Java 21 才可能被大规模使用。

每年发布两个正式版本,分别是 3 月份和 9 月份。

在 Java 版本中,一个特性的发布都会经历孵化阶段、预览阶段和正式版本。其中孵化和预览可能会跨越多个 Java 版本。所以在介绍 Java 新特性时采用如下这种策略:

  1. 每个版本的新特性,都会做一个简单的概述。
  2. 单独出文介绍跟编码相关的新特性,一些如 JVM、性能优化的新特性不单独出文介绍。
  3. 孵化阶段的新特性不出文介绍。
  4. 首次引入为预览特性、新特性增强、首次引入的正式特性,单独出文做详细介绍。
  5. 影响比较大的新特性如果在现阶段没有转正的新特性不单独出文介绍,单独出文的重大特性一般都在 Java 21 版本之前已转为正式特性,例如:
    • 虚拟线程,Java 19 引入的,在 Java 21 转正,所以在 Java 19 单独出文做详细介绍
    • 作用域值,Java 20 引入的,但是在 Java 21 还处于预览阶段,所以不做介绍,等将来转正后会详细介绍

JEP 120:重复注解@Repeatable

官方文档:
https://openjdk.org/jeps/120

Java 8 之前如何使用重复注

在 Java 8 之前我们是无法在一个类型重复使用多次同一个注解,比如我们常用的 @PropertySource,如果我们在 Java 8 版本以下这样使用:

1
2
3
4
@PropertySource("classpath:config.properties")
@PropertySource("classpath:application.properties")
public class PropertyTest {
}

编译会报错,错误信息是:Duplicate annotation。

那怎么解决这个问题呢?在 Java 8 之前想到一个方案来解决 Duplicate annotation 错误:新增一个注解 @PropertySources,该注解包裹 @PropertySource,如下:

1
2
3
4
public @interface PropertySources {

PropertySource[] value();
}

然后就可以利用 @PropertySources 来完成了:

1
2
3
4
5
6
@PropertySources({
@PropertySource("classpath:config.properties"),
@PropertySource("classpath:application.properties")
})
public class PropertyTest {
}

利用这种嵌套的方式来规避重复注解的问题,怎么获取呢?

1
2
3
4
5
6
7
8
9
10
    @Test
public void test() {
PropertySources propertySources = PropertyTest.class.getAnnotation(PropertySources.class);
for (PropertySource propertySource : propertySources.value()) {
System.out.println(propertySource.value()[0]);
}
}
// 结果......
classpath:config.properties
classpath:application.properties

Java 8 重复注解 @Repeatable

通过上述那种方式确实是可以解决重复注解的问题,但是使用有点儿啰嗦,所以 Java 8 为了解决这个问题引入了注解 @Repeatable 来解决这个问题。

@Repeatable 注解允许在同一个类型上多次使用相同的注解,它提供了更灵活的注解使用方式。

下面我们来看看如何使用重复注解。

  1. 重复注解声明
    在使用重复注解之前,需要在自定义注解类型上使用@Repeatable注解,以指定该注解可重复使用的容器注解类型。容器注解类型本身也是一个注解,通常具有一个value属性,其值是一个数组,用于存储重复使用的注解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(MyAnnotations.class) // 声明重复注解
public @interface MyAnnotation {
String name() default "";
}

/**
* 重复注解容器
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotations {
MyAnnotation[] value();
}

  1. 使用重复注解
    定义了重复注解,我们就可以在一个类型上面使用多个相同的注解,如下:
1
2
3
4
5
6
7
@MyAnnotation(name = "死磕 Java 并发")
@MyAnnotation(name = "死磕 Netty")
@MyAnnotation(name = "死磕 Redis")
@MyAnnotation(name = "死磕 Java 基础")
@MyAnnotation(name = "死磕 Redis")
public class MyAnnotationTest {
}
  1. 获取重复注解的值
    使用放射获取元素上面的重复注解,由于我们这里有多个所以需要根据 getAnnotationsByType() 来获取所有重复注解的数组:
1
2
3
4
5
6
7
@Test
public void test() {
MyAnnotation[] myAnnotations = MyAnnotationTest.class.getAnnotationsByType(MyAnnotation.class);
for (MyAnnotation myAnnotation : myAnnotations) {
System.out.println(myAnnotation.name());
}
}

我们还可以直接获取它的容器注解:

1
2
3
4
5
6
7
@Test
public void test() {
MyAnnotations myAnnotations = MyAnnotationTest.class.getAnnotation(MyAnnotations.class);
for (MyAnnotation myAnnotation : myAnnotations.value()) {
System.out.println(myAnnotation.name());
}
}

重复注解很容易就理解了,知道如何自定义注解,然后变换下思路就行了。

JEP 122:移除Permgen

  1. 官方文档
    https://openjdk.org/jeps/122

  2. 摘要
    从 Hotspot JVM 中移除永久代,从而不再需要调整永久代的大小。

  3. 非目标
    将类数据共享扩展到应用程序类。减少类元数据所需的内存。启用类元数据的异步收集。

  4. 成功指标
    类元数据、已驻留字符串和类静态变量将从永久代移动到 Java 堆或本地内存中。

将从 Hotspot JVM 中移除永久代的代码。

根据尚未选定的基准测试集测量,应用程序的启动时间和占用空间不会增加超过 1%。

  1. 动机
    这是 JRockit 和 Hotspot 融合工作的一部分。JRockit 客户无需配置永久代(因为 JRockit 没有永久代),并且习惯于不配置永久代。

  2. 描述
    将 Hotspot 中永久代的部分内容移动到 Java 堆,其余部分移动到本地内存。

Hotspot 对 Java 类(此处称为类元数据)的表示当前存储在 Java 堆的一部分中,该部分称为永久代。此外,已驻留字符串和类静态变量也存储在永久代中。永久代由 Hotspot 管理,并且必须为 Java 应用程序使用的所有类元数据、已驻留字符串和类静态变量提供足够的空间。当加载类时,类元数据和静态变量在永久代中分配,并在卸载类时从永久代中垃圾回收。当永久代进行垃圾回收时,已驻留字符串也会被垃圾回收。

提议的实现将在本地内存中分配类元数据,并将已驻留字符串和类静态变量移动到 Java 堆。Hotspot 将显式地为类元数据分配和释放本地内存。新类元数据的分配将受可用本地内存量的限制,而不是由-XX:MaxPermSize 的值(无论是默认值还是命令行上指定的值)固定。

类元数据的本地内存分配将以足够大的块进行,以便能够容纳多个类元数据片段。每个块将与一个类加载器相关联,并且由该类加载器加载的所有类元数据都将由 Hotspot 从该类加载器的块中分配。如有需要,将为类加载器分配额外的块。块的大小将根据应用程序的行为而变化。将选择这些大小以限制内部和外部碎片。当类加载器死亡时,将释放与该类加载器相关联的所有块,从而释放类元数据的空间。在类的生命周期内,类元数据不会被移动。

JEP 126:Lambda 表达式&函数式接口

  1. 官方文档
    https://openjdk.org/jeps/126

  2. 摘要
    添加lambda表达式 (闭包) 和支持功能,包括 方法引用、增强的类型推断和虚拟扩展 方法,以Java编程语言和平台。

  3. 目标
    lambda表达式和虚拟扩展的主要功能 方法,以及它们的一组辅助支持功能, 更多平台目标:

简化了更抽象的创造和消费, 更高性能的库
通过迁移兼容性支持更平滑的库演进
除了为Java编程语言添加一个现在常见的功能之外, lambda表达式为改进多核支持提供了可能性 通过启用内部迭代成语。

围绕lambda的支持语言功能包括虚拟扩展 方法,这将允许接口在源和二进制文件中进化 兼容时尚。

除了语言更改,协调库和JVM更改 也会发生。

请注意,活动的和正在进行的 项目LambdaOpenJDK项目 早于JEP进程,相应的JSR也是如此, JSR 335,这是有针对性的 对于Java SE 8 (JSR 336)。

  1. 非目标
    函数类型和一般控制的语言特征 抽象是不将lambda表达式添加到Java的目标。 然而,目的是不排除增加这样的 未来的特点。

  2. 动机
    许多其他常用的面向对象编程语言, 包括托管在JVM上的那些 (例如Groovy、Ruby和 Scala) 和托管在其他虚拟机 (CLR上的C #) 上,包括 支持闭包。因此,Java程序员越来越 熟悉语言功能和编程模型 启用。

特别感兴趣的是启用以下成语内部迭代。 数组和集合当前支持外部迭代,在哪里 迭代的控制逻辑位于数据结构之外 被穿越。例如,afor-数组上的每个循环或 集合是外部迭代的一个例子。的语义 forJava中的循环要求严格的串行迭代,这意味着 程序员对标准进行迭代的唯一手段 集合将不允许使用所有可用的内核。

通过内部迭代,数据结构被传递一段代码 来执行,作为lambda表达式,数据结构为 负责划分计算并报告 结果。由于数据结构熟悉其自身的内部 详细信息,它可能会选择一个更好的调度 通过调整选项进行计算,例如

备用执行顺序
使用线程池的并发执行
使用分区和工作窃取的并行执行。的 fork/join框架, 在Java SE 7中添加的是一个这样的候选并行执行框架,并且 在广泛的核心范围内提供性能稳定的工作分区 计数。
内部迭代样式的一个典型示例是序列 filter-map-reduce操作,例如:

1
2
3
4
int maxFooWeight =
collection.filter( /* isFoo Predicate as a lambda */)
.map( /* Map a Foo to its weight with a lambda */)
.max(); /* Reduction step */

lambda表达式是具有简洁语法的表达式,用于 表示所需的操作。此样式代替一个或多个 显式for循环,这将不必要的约束迭代 订购收藏品。此外,设计良好的算法可以 不仅可以并行执行这些操作集,而且还可以 将这三个操作聚合到一个并行过程中。

项目Lambda还包括虚拟扩展方法,这将 解决长期存在的无法添加方法的限制 到广泛使用的接口,因为源兼容性问题。

通过向现有的集合接口添加扩展方法, 如java.util.Collection和java.util.List,现有 这些类型的实现可以参与新的编程 成语。JDK (和其他地方) 中这些类型的实现可以 重写扩展方法的默认实现 超级接口,以提供更高性能或以其他方式专用 实现。

  1. 使用
    Lambda表达式和匿名内部类

JEP 126:接口的默认方法

Java 8 用默认方法与静态方法这两个新概念来扩展接口的声明。默认方法使接口有点像 Traits(Scala 中特征(trait)类似于 Java 中的 Interface,但它可以包含实现代码,也就是目前 Java 8 新增的功能),但与传统的接口又有些不一样,它允许在已有的接口中添加新方法,而同时又保持了与旧版本代码的兼容性。

默认方法与抽象方法不同之处在于抽象方法必须要求实现,但是默认方法则没有这个要求。相反,每个接口都必须提供一个所谓的默认实现,这样所有的接口实现者将会默认继承它(如果有必要的话,可以覆盖这个默认实现)。让我们看看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package h.xd.java8;

public class Main {
public static void main(String[] args) {
System.out.println(new DefaultableImpl().notRequired());
System.out.println(new OverridableImpl().notRequired());
}
}

interface Defaulable {
// Interfaces now allow default methods, the implementer may or
// may not implement (override) them.
default String notRequired() {
return "Default implementation";
}

}

class DefaultableImpl implements Defaulable {

}

class OverridableImpl implements Defaulable {

@Override
public String notRequired() {
return "Overridden implementation";
}
}

Defaulable 接口用关键字 default 声明了一个默认方法 notRequired(),Defaulable 接口的实现者之一 DefaultableImpl 实现了这个接口,并且让默认方法保持原样。Defaulable 接口的另一个实现者 OverridableImpl 用自己的方法覆盖了默认方法。

Java 8 带来的另一个有趣的特性是接口可以声明(并且可以提供实现)静态方法。例如:

1
2
3
4
5
6
interface DefaulableFactory {
// Interfaces now allow static methods
static Defaulable create( Supplier< Defaulable > supplier ) {
return supplier.get();
}
}

下面的一小段代码片段把上面的默认方法与静态方法黏合到一起。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package h.xd.java8;

import java.util.function.Supplier;

public class Main {
public static void main( String[] args ) {
Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
System.out.println( defaulable.notRequired() );
defaulable = DefaulableFactory.create( OverridableImpl::new );
System.out.println( defaulable.notRequired() );
}

}

interface Defaulable {
// Interfaces now allow default methods, the implementer may or
// may not implement (override) them.
default String notRequired() {
return "Default implementation";
}

}

class DefaultableImpl implements Defaulable {

}

class OverridableImpl implements Defaulable {

@Override
public String notRequired() {
return "Overridden implementation";
}
}

interface DefaulableFactory {
// Interfaces now allow static methods
static Defaulable create( Supplier< Defaulable > supplier ) {
return supplier.get();
}
}

这个程序的控制台输出如下:

1
2
3
Connected to the target VM, address: '127.0.0.1:57010', transport: 'socket'
Default implementation
Overridden implementation

在JVM中,默认方法的实现是非常高效的,并且通过字节码指令为方法调用提供了支持。默认方法允许继续使用现有的Java接口,而同时能够保障正常的编译过程。这方面好的例子是大量的方法被添加到 java.util.Collection 接口中去:stream(),parallelStream(),forEach(),removeIf(),……

尽管默认方法非常强大,但是在使用默认方法时我们需要小心注意一个地方:在声明一个默认方法前,请仔细思考是不是真的有必要使用默认方法,因为默认方法会带给程序歧义,并且在复杂的继承体系中容易产生编译错误。更多详情请参考官方文档。

JEP 150:新的日期时间 API && 日期时间格式化

JDK8之前,Java的日期和时间API存在以下问题:

设计很差:在java.util和java.sql中都有日期类,java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期。此外,用于格式化和解析的类定义在java.text包中。
非线程安全:java.util.Date是非线程安全的,所有的日期类都是可变的,这是Java日期类的最大问题之一。
时区处理麻烦:日期类并不提供国际化,没有时区支持,虽然后来引入了java.util.Calendar和java.util.TimeZone类,但他们也存在上述所有问题。

为了解决这些问题,JDK8借鉴了Joda Time的设计,引入了一套全新的日期和时间API,这些API设计合理,线程安全,都位于java.time包中,下面是一些关键类。

1
2
3
4
5
6
7
8
9
10
LocalDate:表示日期,包含年月日,格式为 2024-01-02
LocalTime:表示时间,包含时分秒,格式为 23:01:28.123456789
LocalDateTime:表示日期时间,包含年月日 时分秒,格式为 2024-01-02T23:01:28.123456789
DateTimeFormatter:日期时间的格式化类

Instant:时间戳,表示时间线上的一个特定瞬间
Period:用于计算2个日期(LocalDate)的距离
Duration:用于计算2个时间(LocalTime)的距离

ZonedDateTime:包含时区的时间

Java使用的日历系统是ISO 8601,它是世界民用历法,也就是通常讲的公历。平年有365天,闰年有366天。

LocalDate、LocalTime、LocalDateTime类的实例是不可变对象,分别表示ISO 8601日历系统的日期、时间、日期和时间。他们提供了简单的日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。

LocalDate

日期,格式为:2024-01-02。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 获取当前日期
LocalDate nowDay = LocalDate.now();

// 创建指定日期
LocalDate theDay = LocalDate.of(2024, 1, 2);

// 获取LocalDateTime对象的日期
LocalDate dayFromDateTime = LocalDate.from(LocalDateTime.now());

// 解析字符串获取日期
LocalDate parsedDay = LocalDate.parse("2024-01-02");

// 获取LocalDate对象调整后的日期
LocalDate nextMonday = theDay.with(TemporalAdjusters.next(DayOfWeek.MONDAY));

// 获取日期信息
System.out.println("年:" + nowDay.getYear());
System.out.println("月:" + nowDay.getMonthValue());
System.out.println("日:" + nowDay.getDayOfMonth());
System.out.println("星期:" + nowDay.getDayOfWeek().getValue());

// 是否为闰年
nowDay.isLeapYear();

// 修改日期
nowDay.plusYears(1);
nowDay.plusMonths(1);
nowDay.plusWeeks(1);
nowDay.plusDays(2);
nowDay.minusYears(1);
nowDay.minusMonths(1);
nowDay.minusWeeks(1);
nowDay.minusDays(2);

// 日期比较
nowDay.isBefore(tomorrow); // true
nowDay.isEqual(tomorrow); // false
nowDay.isAfter(tomorrow); // false

LocalTime

时间,格式为:23:01:28.123456789。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 获取当前时间
LocalTime now = LocalTime.now();

// 创建指定时间
LocalTime theTime = LocalTime.of(18, 20, 16);

// 获取LocalDateTime对象的时间
LocalTime TimeFromDateTime = LocalTime.from(LocalDateTime.now());

// 解析字符串获取时间
LocalTime parsedTime = LocalTime.parse("22:25:05");

// 获取LocalTime对象调整后的时间
theTime.withHour(20);
theTime.withMinute(30);
theTime.withMinute(15);

// 获取时间信息
System.out.println("时:" + now.getHour());
System.out.println("分:" + now.getMinute());
System.out.println("秒:" + now.getSecond());
System.out.println("纳秒:" + now.getNano());

// 修改时间
now.plusHours(1);
now.plusMinutes(1);
now.plusSeconds(10);
now.plusNanos(1000);
now.minusHours(1);
now.minusMinutes(1);
now.minusSeconds(10);
now.minusNanos(1000);

// 时间比较
now.isBefore(nextHour); // true
now.isAfter(nextHour); // false
now.equals(nextHour); // false

LocalDateTime

日期和时间,格式为:2024-01-02T22:24:05.123456789。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 获取当前日期时间
LocalDateTime now = LocalDateTime.now();

// 创建指定日期时间
LocalDateTime theDateTime = LocalDateTime.of(2024, 1, 2, 22, 5, 18);

// 根据另一个LocalDateTime对象创建日期时间
LocalDateTime dateTimeFromAnother = LocalDateTime.from(LocalDateTime.now());

// 解析字符串获取日期时间
LocalDateTime parsedDateTime = LocalDateTime.parse("2024-01-02T22:29:23.787");
LocalDateTime parsedDateTime = LocalDateTime.parse("2024-01-02 22:29:23",
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

// 获取LocalDateTime对象调整后的日期时间
LocalDateTime nextMonth = now.with(TemporalAdjusters.firstDayOfNextMonth());

// 获取时间信息
System.out.println("年:" + now.getYear());
System.out.println("月:" + now.getMonthValue());
System.out.println("日:" + now.getDayOfMonth());
System.out.println("星期:" + now.getDayOfWeek().getValue());
System.out.println("时:" + now.getHour());
System.out.println("分:" + now.getMinute());
System.out.println("秒:" + now.getSecond());
System.out.println("纳秒:" + now.getNano());

// 格式化LocalDateTime
now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

// 桉指定时间单位保留精度
now = now.truncatedTo(ChronoUnit.HOURS);

// 修改LocalDateTime
now.plusYears(1);
now.plusMonths(1);
now.plusWeeks(1);
now.plusDays(2);
now.plusHours(1);
now.plusMinutes(1);
now.plusSeconds(10);
now.plusNanos(1000);
now.minusYears(1);
now.minusMonths(1);
now.minusWeeks(1);
now.minusDays(2);
now.minusHours(1);
now.minusMinutes(1);
now.minusSeconds(10);
now.minusNanos(1000);

// LocalDateTime比较
now.isBefore(nextHour); // true
now.isEqual(nextHour); // false
now.isAfter(nextHour); // false

// LocalDateTime转为ZonedDateTime
ZonedDateTime zonedDateTime = now.atZone(ZoneId.of("+08"));

// LocalDateTime转为OffsetDateTime
OffsetDateTime offsetDateTime = now.atOffset(ZoneOffset.ofHours(8));

时间格式化与解析

java.time.format.DateTimeFormatter类专门负责日期时间的格式化与解析,这个类是final的,线程安全。

1
2
3
4
5
6
7
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

// 格式化LocalDateTime为字符串
System.out.println(LocalDateTime.now().format(fmt));

// 解析时间字符串
LocalDateTime parsedDateTime = LocalDateTime.parse("2024-01-02 10:20:30", fmt);

时间戳

Instant类,时间戳,表示时间线上的一个特定瞬间。内部保存了从1970年1月1日 00:00:00以来的秒和纳秒。

一般不是给用户使用的,而是方便程序做一些统计。

1
2
3
4
5
6
7
8
9
10
11
12
13
Instant instant = Instant.now();
// 当前时间戳:2024-01-02T15:51:12.068Z
System.out.println("当前时间戳:" + instant);

// 1970-01-01 00:00:00 到现在的秒:1716565872
System.out.println("1970-01-01 00:00:00 到现在的秒:" + instant.getEpochSecond());
// 1970-01-01 00:00:00 到现在的毫秒:1716565872068
System.out.println("1970-01-01 00:00:00 到现在的毫秒:" + instant.toEpochMilli());
// 秒后面的纳秒:68000000
System.out.println("秒后面的纳秒:" + instant.getNano());

Instant second = Instant.ofEpochSecond(1716565310);
System.out.println(second); // 2024-01-02T15:41:50Z

计算日期、时间差

Duration:用于计算2个时间的距离;

Period:用于计算2个日期的距离。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
LocalDateTime now = LocalDateTime.now();
LocalDateTime nextMonday = now.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
// Duration 计算时间的距离
Duration duration = Duration.between(now, nextMonday);
System.out.println("相差的天:" + duration.toDays());
System.out.println("相差的小时:" + duration.toHours());
System.out.println("相差的分钟:" + duration.toMinutes());
System.out.println("相差的秒:" + duration.getSeconds());
System.out.println("相差的毫秒:" + duration.toMillis());

LocalDate today = LocalDate.now();
LocalDate nextTuesday = today.with(TemporalAdjusters.next(DayOfWeek.TUESDAY));
// Period 计算日期的距离
Period period = Period.between(today, nextTuesday);
System.out.println("相差的年:" + period.getYears());
System.out.println("相差的月:" + period.getMonths());
System.out.println("相差的天:" + period.getDays());

时间调整器

有时候我们可能需要根据一个LocalDateTime获取它调整后的时间,比如“下个月的第2天”,“下个周三”,等等。类似的时间可以通过时间调整器获取。

TemporalAdjuster:时间调整器接口。

TemporalAdjusters:该类通过静态方法提供了很多常用的TemporalAdjuster的实现。

1
2
3
4
5
6
7
8
9
10
// 将 LocalDateTime 调整到下个月第2天
TemporalAdjuster secondDayOfNextMonth = temporal -> {
LocalDateTime dateTime = (LocalDateTime) temporal;
return dateTime.plusMonths(1).withDayOfMonth(2);
};

LocalDateTime now = LocalDateTime.now();
System.out.println(now); // 2024-01-02T12:17:01.524
System.out.println(now.with(secondDayOfNextMonth)); // 2024-02-02T12:17:01.524

JDK自带的时间调整器实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 当月第1天
TemporalAdjusters.firstDayOfMonth()
// 当月最后1天
TemporalAdjusters.lastDayOfMonth()
// 下月第1天
TemporalAdjusters.firstDayOfNextMonth()

// 当年第1天
TemporalAdjusters.firstDayOfYear()
// 当年最后1天
TemporalAdjusters.lastDayOfYear()
// 下年第1天
TemporalAdjusters.firstDayOfNextYear()

// 当月第1个周几
TemporalAdjusters.firstInMonth(DayOfWeek dayOfWeek)
// 当月最后1个周几
TemporalAdjusters.lastInMonth(DayOfWeek dayOfWeek)

// 当前时间之后的下1个周几
TemporalAdjusters.next(DayOfWeek dayOfWeek)
// 当前时间或者之后的下1个周几
TemporalAdjusters.nextOrSame(DayOfWeek dayOfWeek)
// 当前时间之前的上1个周几
TemporalAdjusters.previous(DayOfWeek dayOfWeek)
// 当前时间或者之前的上1个周几
TemporalAdjusters.previousOrSame(DayOfWeek dayOfWeek)

时区

JDK8加入了对时区的支持,LocalDate、LocalTime、LocalDateTime是不带时区的,带时区的日期时间类分别为:ZonedDateTime。

ZoneId类包含了时区信息,每个时区对应一个ID,格式为“区域/城市”,比如:Asia/Shanghai。

东八区代表的ZoneId可通过以下多种方式创建:

1
2
3
4
5
6
7
ZoneId.of("Asia/Shanghai");
ZoneId.of("+8");
ZoneId.of("+08");
ZoneId.of("+08:00");
ZoneId.of("UTC+8");
ZoneId.of("UTC+08");
ZoneId.of("UTC+08:00");

ZonedDateTime的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 打印所有可用的时区
ZoneId.getAvailableZoneIds().forEach(System.out::println);

LocalDateTime now = LocalDateTime.now();
// 不带时区的当前时间:2024-01-02T12:42:44.550
System.out.println(now);
ZonedDateTime zonedNow = ZonedDateTime.now();
// 当前时区的当前时间:2024-01-02T12:42:44.550+08:00[Asia/Shanghai]
System.out.println(zonedNow);

// UTC的当前时间:2024-01-02T04:44:39.496Z
ZonedDateTime utcNow = ZonedDateTime.now(Clock.systemUTC());
System.out.println(utcNow);

// UTC时间转为东八区时间:2024-01-02T12:44:39.496+08:00
ZonedDateTime utc8Now = utcNow.withZoneSameInstant(ZoneId.of("+08"));
System.out.println(utc8Now);

JEP 174:Nashorn JavaScript 引擎

官方文档:
https://openjdk.org/jeps/174

java重执行js脚本的引擎

摘要

设计和实现一个新的轻量级,高性能的实现 的JavaScript,并将其集成到JDK中。新引擎将 通过现有的可用于Java应用程序javax.scriptAPI, 也更一般地通过一个新的命令行工具。

目标

Nashorn将基于ECMAScript-262版5.1语言 规范,并且必须通过ECMAScript-262符合性测试。

Nashorn将支持javax.script(JSR 223) API。

将提供从JavaScript调用Java代码的支持 为Java调用JavaScript代码。这包括直接映射到 JavaBeans。

Nashorn将定义一个新的命令行工具,jjs,用于评估 “shebang” 脚本中的JavaScript代码,此处文档和编辑 字符串。

Nashorn应用程序的性能和内存使用情况应该是 比犀牛好得多。

Nashorn不会暴露任何额外的安全风险。

提供的库应在本地化下正确运行。

错误消息和文档将被国际化。

JEP 179:方法引用和构造器引用

参考lambda表达式介绍

利用 Optional 解决NullPointerException

全新的、标准的 Base64 API

  • 本文标题:Java 8 新特性二
  • 本文作者:形而上
  • 创建时间:2024-01-02 14:23:45
  • 本文链接:https://deepter.gitee.io/2024_01_02_java/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!