Files
www/blogs/java/java8/java8-4.md
zhuhjay 22e48d9558 build(www): 添加 Drone CI 流水线配置
- 新增 .drone.yml 文件用于定义 CI/CD 流程
- 配置了基于 Docker 的部署步骤
- 设置了工作区和卷映射以支持持久化数据
- 添加了构建准备阶段和 Docker 部署阶段
- 定义了环境变量和代理设置
- 配置了 artifacts 目录的处理逻辑
- 添加了 timezone 映射以确保时间同步
- 设置了 docker.sock 映射以支持 Docker in Docker
2025-11-01 13:36:00 +08:00

446 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: JDK8 特性(四)
date: 2022-05-26
sidebar: 'auto'
tags:
- JDK8
categories:
- Java
---
## 1 Optional
### 1.1 Optional介绍
- Optional是一个没有子类的工具类Optional是一个可以为null的容器对象。它的作用主要就是为了解决避免Null检查防止NullPointerException。
### 1.2 Optional基本使用
- Optional类的创建方式
```text
Optional.of(T t) : 创建一个 Optional 实例, 不能为空
Optional.empty() : 创建一个空的 Optional 实例
Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
```
- Optional类的常用方法
```text
isPresent() : 判断是否包含值,包含值返回true不包含值返回false
get() : 如果Optional有值则将其返回否则抛出NoSuchElementException
orElse(T t) : 如果调用对象包含值返回该值否则返回参数t
orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
map(Function f): 如果有值对其处理并返回处理后的Optional否则返回 Optional.empty()
```
### 1.3 Optional高级使用
```java
public class OptionalTest {
@Test
public void ifPresent(){
// Optional<User> op = Optional.of(new User("张三", 18));
Optional<User> op = Optional.empty();
// 如果存在就做点什么, 没有就不做了
op.ifPresent(user -> {
System.out.println(user.toString());
});
}
@Test
public void map(){
User user = new User("迪丽热巴", 20);
// 判断如果存在, 那么就将名字重新拼接
Optional<User> optional = Optional.of(user);
System.out.println(getNewName(optional));
}
private String getNewName(Optional<User> optional){
return optional.map(User::getUsername)
.map(str -> str.substring(2))
.orElse("null");
}
}
```
## 2 新的日期时间API
- 旧版日期时间API存在的问题
1. 设计很差:在`java.util`和`java.sql`的包中都有日期类,`java.util.Date`同时包含日期和时间,而`java.sql.Date`仅包含日期。此外用于格式化和解析的类在`java.text`包中定义。
2. 非线程安全:`java.util.Date`是非线程安全的所有的日期类都是可变的这是Java日期类最大的问题之一。
3. 时区处理麻烦日期类并不提供国际化没有时区支持因此Java引入了`java.util.Calendar`和 `java.util.TimeZone`类,但他们同样存在上述所有的问题。
### 2.1 新的日期时间API介绍
- JDK 8中增加了一套全新的日期时间API这套API设计合理是线程安全的。新的日期及时间API位于 `java.time`包中,下面是一些关键类。
- LocalDate :表示日期,包含年月日,格式为 2019-10-16
- LocalTime :表示时间,包含时分秒,格式为 16:38:54.158549300
- LocalDateTime :表示日期时间,包含年月日,时分秒,格式为 2018-09-06T15:33:56.750
- DateTimeFormatter :日期时间格式化类。
- Instant时间戳表示一个特定的时间瞬间。
- Duration用于计算2个时间(LocalTime时分秒)的距离
- Period用于计算2个日期(LocalDate年月日)的距离
- ZonedDateTime :包含时区的时间
- Java中使用的历法是ISO 8601日历系统它是世界民用历法也就是我们所说的公历。平年有365天闰年是366天。此外Java 8还提供了4套其他历法分别是
- ThaiBuddhistDate泰国佛教历
- MinguoDate中华民国历
- JapaneseDate日本历
- HijrahDate伊斯兰历
### 2.2 日期时间类
- LocalDate、LocalTime、LocalDateTime类的实例是不可变的对象分别表示使用 ISO-8601 日历系统的日期、时 间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。
- LocalDate: 获取日期的信息
```java
public class DateTest {
@Test
public void localDate(){
// 创建指定日期
LocalDate localDate = LocalDate.of(2022, 11, 11);
System.out.println("localDate = " + localDate); // localDate = 2022-11-11
// 得到当前日期
LocalDate now = LocalDate.now();
System.out.println("now = " + now); // now = 2022-05-23
// 获取日期信息
System.out.println("年: " + now.getYear()); // 年: 2022
System.out.println("月: " + now.getMonthValue()); // 月: 5
System.out.println("日: " + now.getDayOfMonth()); // 日: 23
System.out.println("星期: " + now.getDayOfWeek()); // 星期: MONDAY
}
}
```
- LocalTime: 获取时间信息
```java
public class DateTest {
@Test
public void localTime(){
// 得到指定的时间
LocalTime time = LocalTime.of(12, 12, 12);
System.out.println("time = " + time); // time = 12:12:12
// 得到当前时间
LocalTime now = LocalTime.now();
System.out.println("now = " + now); // now = 11:43:10.495
// 获取时间信息
System.out.println("小时: " + now.getHour()); // 小时: 11
System.out.println("分钟: " + now.getMinute()); // 分钟: 43
System.out.println("秒: " + now.getSecond()); // 秒: 10
System.out.println("纳秒: " + now.getNano()); // 纳秒: 495000000
}
}
```
- LocalDateTime: 获取日期时间类型
```java
public class DateTest {
@Test
public void localDateTime(){
LocalDateTime time = LocalDateTime.of(2022, 11, 11, 12, 12, 12);
System.out.println("time = " + time); // time = 2022-11-11T12:12:12
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now); // now = 2022-05-23T11:47:29.308
// 获取日期信息
System.out.println("年: " + now.getYear()); // 年: 2022
System.out.println("月: " + now.getMonthValue()); // 月: 5
System.out.println("日: " + now.getDayOfMonth()); // 日: 23
System.out.println("时: " + now.getHour()); // 时: 11
System.out.println("分: " + now.getMinute()); // 分: 47
System.out.println("秒: " + now.getSecond()); // 秒: 29
System.out.println("纳秒: " + now.getNano()); // 纳秒: 308000000
}
}
```
- 对日期时间的修改对已存在的LocalDate对象创建它的修改版最简单的方式是使用withAttribute方法。 withAttribute方法会创建对象的一个副本并按照需要修改它的属性。以下所有的方法都返回了一个修改属性的对象他们不会影响原来的对象。
```java
public class DateTest {
@Test
public void localDateTimeUpdate(){
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now); // now = 2022-05-23T12:47:18.640
// 修改年份, 每份修改后的数据都是一个新的对象, 不会与原对象冲突
LocalDateTime setYear = now.withYear(2025);
System.out.println("修改年份后 = " + setYear); // 修改年份后 = 2025-05-23T12:47:18.640
System.out.println("setYear == now? " + (setYear == now)); // setYear == now? false
System.out.println("修改月份: " + now.withMonth(1)); // 修改月份: 2022-01-23T12:47:18.640
System.out.println("修改日: " + now.withDayOfMonth(1)); // 修改日: 2022-05-01T12:47:18.640
System.out.println("修改小时: " + now.withMonth(12)); // 修改小时: 2022-12-23T12:47:18.640
// 在当前对象的基础上加上或减去指定的时间
LocalDateTime localDateTime = now.plusDays(5);
System.out.println("五天后: " + localDateTime); // 五天后: 2022-05-28T12:47:18.640
System.out.println("10年后: " + now.plusYears(10)); // 10年后: 2032-05-23T12:47:18.640
System.out.println("20年后: " + now.plusYears(20)); // 20年后: 2042-05-23T12:47:18.640
System.out.println("3个月前: " + now.minusMonths(3)); // 3个月前: 2022-02-23T12:47:18.640
System.out.println("3分钟前: " + now.minusMinutes(3)); // 3分钟前: 2022-05-23T12:44:18.640
}
}
```
- 日期比较
```java
public class DateTest {
@Test
public void dateEqual(){
// 在JDK8中LocalDate类中使用isBefore()、isAfter()、equals()方法来比较两个日期,可直接进行比较。
LocalDate now = LocalDate.now();
LocalDate date = LocalDate.of(2011, 11, 11);
System.out.println(now.isBefore(date)); // false
System.out.println(date.isBefore(now)); // true
}
}
```
### 2.3 日期格式化与解析
- 通过`java.time.format.DateTimeFormatter`类可以进行日期时间解析与格式化。
```java
public class DateTest {
@Test
public void dateFormatAndParse(){
// 获得当前的日期
LocalDateTime now = LocalDateTime.now();
// 自定义日期格式
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy.MM.dd & HH:mm:ss");
// 将日期转换为对应的格式
String format = dateTimeFormatter.format(now);
System.out.println("format = " + format);
// 将字符串解析为时间
LocalDateTime time = LocalDateTime.parse(format, dateTimeFormatter);
System.out.println("time = " + time);
}
}
```
### 2.4 Instant类
- Instant 时间戳/时间线内部保存了从1970年1月1日 00:00:00以来的秒和纳秒。
- 只能操作秒以下的级别, 用来程序做一些统计的类, 比如说计算运行时间等, 不是给用户使用的
```java
public class InstantTest {
@Test
public void instant(){
Instant now = Instant.now();
System.out.println("当前时间戳 = " + now);
// 获取从1970 01 01 00:00:00的秒
System.out.println(now.getNano());
System.out.println(now.getEpochSecond());
System.out.println(now.toEpochMilli());
System.out.println(System.currentTimeMillis());
System.out.println("5s以后: " + now.plusSeconds(5));
Instant instant = Instant.ofEpochSecond(5);
System.out.println("instant = " + instant);
}
}
```
### 2.5 计算日期时间差的类
- Duration/Period类: 计算日期时间差。
1. Duration用于计算2个时间(LocalTime时分秒)的距离
2. Period用于计算2个日期(LocalDate年月日)的距离
- 注意: 两个类的计算都是第二个参数减去第一个参数, 所以当参数对调时会产生负数的情况
```java
public class DifferenceDateTest {
@Test
public void test(){
// 获取当前时间
LocalTime now = LocalTime.now();
LocalTime of = LocalTime.of(12, 12, 12);
// 使用Duration计算时间的间隔
Duration duration = Duration.between(of, now);
System.out.println("相差的天数" + duration.toDays());
System.out.println("相差的小时数" + duration.toHours());
System.out.println("相差的分钟数" + duration.toMinutes());
System.out.println("相差的秒数" + duration.toMillis());
// 获取当前的日期
LocalDate date = LocalDate.now();
LocalDate localDate = LocalDate.of(2021, 2, 2);
// 使用Period计算日期的间隔
Period period = Period.between(localDate, date);
System.out.println("相差的年" + period.getYears());
System.out.println("相差的月" + period.getMonths());
System.out.println("相差的日" + period.getDays());
}
}
```
### 2.6 时间校正器
- 有时我们可能需要获取例如:将日期调整到“下一个月的第一天”等操作。可以通过时间校正器来进行。
- TemporalAdjuster : 时间校正器。
- TemporalAdjusters : 该类通过静态方法提供了大量的常用TemporalAdjuster的实现。
```java
public class Demo{
@Test
public void temporalAdjuster(){
LocalDateTime now = LocalDateTime.now();
// 自定义时间调整器
LocalDateTime with = now.with(temporal -> {
LocalDateTime dateTime = (LocalDateTime) temporal;
// 修改到下个月1号
return dateTime.plusMonths(1).withDayOfMonth(1);
});
System.out.println("with = " + with);
}
}
```
### 2.7 设置日期时间的时区
- Java8 中加入了对时区的支持LocalDate、LocalTime、LocalDateTime是不带时区的带时区的日期时间类分别为ZonedDate、ZonedTime、ZonedDateTime。
- 其中每个时区都对应着IDID的格式为 “区域/城市” 。例如 Asia/Shanghai 等。
- ZoneId该类中包含了所有的时区信息。
```java
public class Demo{
@Test
public void test(){
// 获取所有的时区ID
ZoneId.getAvailableZoneIds().forEach(System.out::println);
// 不带时区获取计算机的当前时间
// 中国默认时区东八区, 比标准时间快8h
LocalDateTime now = LocalDateTime.now();
System.out.println("now = " + now);
// 操作带时区的类
// 创建世界标准时间, 不带参数则使用计算机默认的时区
ZonedDateTime zonedDateTime = ZonedDateTime.now(Clock.systemUTC());
System.out.println("zonedDateTime = " + zonedDateTime);
System.out.println("指定时区获取时间: " + ZonedDateTime.now(ZoneId.of("Europe/London")));
}
}
```
### 2.8 小结
- 详细学习了新的日期是时间相关类LocalDate表示日期,包含年月日,LocalTime表示时间,包含时分 秒,`LocalDateTime = LocalDate + LocalTime`,时间的格式化和解析,通过DateTimeFormatter类型进行.
- 学习了Instant类,方便操作秒和纳秒,一般是给程序使用的.学习Duration/Period计算日期或时间的距离,还使用时间调整器方便的调整时间,学习了带时区的3个类ZoneDate/ZoneTime/ZoneDateTime
- JDK 8新的日期和时间API的优势
1. 新版的日期和时间API中日期和时间对象是不可变的。操纵的日期不会影响老值而是新生成一个实例。
2. 新的API提供了两种不同的时间表示方式有效地区分了人和机器的不同需求。
3. TemporalAdjuster可以更精确的操纵日期还可以自定义日期调整器。
4. 是线程安全的
## 3 重复注解与类型注释
### 3.1 重复注解的使用
- 自从Java 5中引入注解以来注解开始变得非常流行并在各个框架和项目中被广泛使用。不过注解有一个很大的限制是在同一个地方不能多次使用同一个注解。JDK 8引入了重复注解的概念允许在同一个地方多次使用同一个注解。在JDK 8中使用`@Repeatable`注解定义重复注解。
- 重复注解的使用步骤:
1. 定义重复的注解容器注解
2. 定义一个可以重复的注解
3. 配置多个重复的注解
4. 解析得到指定注解
```java
/** 3. 配置多个重复的注解 **/
@MyTest("AAA")
@MyTest("BBB")
@MyTest("CCC")
public class RepeatableTest {
/** 3. 配置多个重复的注解 **/
@Test
@MyTest("DD")
@MyTest("EE")
public void test(){}
public static void main(String[] args) throws NoSuchMethodException {
// 4. 解析得到指定注解
// getAnnotationsByType是新增API用来获取重复注解的
// 获取类上的注解
MyTest[] annotation = RepeatableTest.class.getAnnotationsByType(MyTest.class);
for (MyTest myTest : annotation) {
System.out.println("myTest = " + myTest.value());
}
// 获取方法上的注解
MyTest[] tests = RepeatableTest.class.getMethod("test").getAnnotationsByType(MyTest.class);
for (MyTest myTest : tests) {
System.out.println("myTest = " + myTest.value());
}
}
}
/**
* 1. 定义重复的注解容器注解
*/
@Retention(RetentionPolicy.RUNTIME)
@interface MyTests{
MyTest[] value();
}
/**
* 2. 定义一个可以重复的注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyTests.class)
@interface MyTest{
String value();
}
```
### 3.2 类型注解的使用
- JDK 8为@Target元注解新增了两种类型TYPE_PARAMETERTYPE_USE 。
- TYPE_PARAMETER :表示该注解能写在类型参数的声明语句中。
- TYPE_USE :表示注解可以再任何用到类型的地方使用。
```java
public class TypeAnnotationDemo<@TypeParam T> {
private @NotNull int one = 10;
public <@TypeParam E extends Integer> void test(@NotNull E ele){}
public static void main(String[] args) {
@NotNull int x = 2;
@NotNull String str = new @NotNull String();
}
}
/**
* 该注解能用到类型前面
*/
@Target(ElementType.TYPE_USE)
@interface NotNull {}
/**
* 该注解能写在泛型上
*/
@Target(ElementType.TYPE_PARAMETER)
@interface TypeParam {}
```