JDK8 特性(四)
# 1 Optional
# 1.1 Optional介绍
- Optional是一个没有子类的工具类,Optional是一个可以为null的容器对象。它的作用主要就是为了解决避免Null检查,防止NullPointerException。
# 1.2 Optional基本使用
Optional类的创建方式
Optional.of(T t) : 创建一个 Optional 实例, 不能为空 Optional.empty() : 创建一个空的 Optional 实例 Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
1
2
3Optional类的常用方法
isPresent() : 判断是否包含值,包含值返回true,不包含值返回false get() : 如果Optional有值则将其返回,否则抛出NoSuchElementException orElse(T t) : 如果调用对象包含值,返回该值,否则返回参数t orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值 map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
1
2
3
4
5
# 1.3 Optional高级使用
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 2 新的日期时间API
- 旧版日期时间API存在的问题
- 设计很差:在
java.util
和java.sql
的包中都有日期类,java.util.Date
同时包含日期和时间,而java.sql.Date
仅包含日期。此外用于格式化和解析的类在java.text
包中定义。 - 非线程安全:
java.util.Date
是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。 - 时区处理麻烦:日期类并不提供国际化,没有时区支持,因此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: 获取日期的信息
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
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- LocalTime: 获取时间信息
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
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- LocalDateTime: 获取日期时间类型
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
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 对日期时间的修改,对已存在的LocalDate对象,创建它的修改版,最简单的方式是使用withAttribute方法。 withAttribute方法会创建对象的一个副本,并按照需要修改它的属性。以下所有的方法都返回了一个修改属性的对象,他们不会影响原来的对象。
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
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- 日期比较
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
4
5
6
7
8
9
10
11
# 2.3 日期格式化与解析
- 通过
java.time.format.DateTimeFormatter
类可以进行日期时间解析与格式化。
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 2.4 Instant类
- Instant 时间戳/时间线,内部保存了从1970年1月1日 00:00:00以来的秒和纳秒。
- 只能操作秒以下的级别, 用来程序做一些统计的类, 比如说计算运行时间等, 不是给用户使用的
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 2.5 计算日期时间差的类
Duration/Period类: 计算日期时间差。
- Duration:用于计算2个时间(LocalTime,时分秒)的距离
- Period:用于计算2个日期(LocalDate,年月日)的距离
注意: 两个类的计算都是第二个参数减去第一个参数, 所以当参数对调时会产生负数的情况
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 2.6 时间校正器
- 有时我们可能需要获取例如:将日期调整到“下一个月的第一天”等操作。可以通过时间校正器来进行。
- TemporalAdjuster : 时间校正器。
- TemporalAdjusters : 该类通过静态方法提供了大量的常用TemporalAdjuster的实现。
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
3
4
5
6
7
8
9
10
11
12
13
14
# 2.7 设置日期时间的时区
- Java8 中加入了对时区的支持,LocalDate、LocalTime、LocalDateTime是不带时区的,带时区的日期时间类分别为:ZonedDate、ZonedTime、ZonedDateTime。
- 其中每个时区都对应着ID,ID的格式为 “区域/城市” 。例如 :Asia/Shanghai 等。
- ZoneId:该类中包含了所有的时区信息。
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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 2.8 小结
详细学习了新的日期是时间相关类,LocalDate表示日期,包含年月日,LocalTime表示时间,包含时分 秒,
LocalDateTime = LocalDate + LocalTime
,时间的格式化和解析,通过DateTimeFormatter类型进行.学习了Instant类,方便操作秒和纳秒,一般是给程序使用的.学习Duration/Period计算日期或时间的距离,还使用时间调整器方便的调整时间,学习了带时区的3个类ZoneDate/ZoneTime/ZoneDateTime
JDK 8新的日期和时间API的优势:
- 新版的日期和时间API中,日期和时间对象是不可变的。操纵的日期不会影响老值,而是新生成一个实例。
- 新的API提供了两种不同的时间表示方式,有效地区分了人和机器的不同需求。
- TemporalAdjuster可以更精确的操纵日期,还可以自定义日期调整器。
- 是线程安全的
# 3 重复注解与类型注释
# 3.1 重复注解的使用
自从Java 5中引入注解以来,注解开始变得非常流行,并在各个框架和项目中被广泛使用。不过注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。JDK 8引入了重复注解的概念,允许在同一个地方多次使用同一个注解。在JDK 8中使用
@Repeatable
注解定义重复注解。重复注解的使用步骤:
- 定义重复的注解容器注解
- 定义一个可以重复的注解
- 配置多个重复的注解
- 解析得到指定注解
/** 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();
}
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
# 3.2 类型注解的使用
- JDK 8为@Target元注解新增了两种类型:TYPE_PARAMETER,TYPE_USE 。
- TYPE_PARAMETER :表示该注解能写在类型参数的声明语句中。
- TYPE_USE :表示注解可以再任何用到类型的地方使用。
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 {}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20