JDK8 特性(四)

2022/5/26 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
    3
  • Optional类的常用方法

    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");
    }
}
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

# 2 新的日期时间API

  • 旧版日期时间API存在的问题
    1. 设计很差:在java.utiljava.sql的包中都有日期类,java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期。此外用于格式化和解析的类在java.text包中定义。
    2. 非线程安全:java.util.Date是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
    3. 时区处理麻烦:日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendarjava.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
    }
}
1
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
    }
}
1
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
    }
}
1
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
    }
}
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
  • 日期比较
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
    }
}
1
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);
    }
}
1
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);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 2.5 计算日期时间差的类

  • Duration/Period类: 计算日期时间差。

    1. Duration:用于计算2个时间(LocalTime,时分秒)的距离
    2. 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());
    }
}
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

# 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);
    }
}
1
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")));
    }
}
1
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的优势:

    1. 新版的日期和时间API中,日期和时间对象是不可变的。操纵的日期不会影响老值,而是新生成一个实例。
    2. 新的API提供了两种不同的时间表示方式,有效地区分了人和机器的不同需求。
    3. TemporalAdjuster可以更精确的操纵日期,还可以自定义日期调整器。
    4. 是线程安全的

# 3 重复注解与类型注释

# 3.1 重复注解的使用

  • 自从Java 5中引入注解以来,注解开始变得非常流行,并在各个框架和项目中被广泛使用。不过注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。JDK 8引入了重复注解的概念,允许在同一个地方多次使用同一个注解。在JDK 8中使用@Repeatable注解定义重复注解。

  • 重复注解的使用步骤:

    1. 定义重复的注解容器注解
    2. 定义一个可以重复的注解
    3. 配置多个重复的注解
    4. 解析得到指定注解
/** 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();
}
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

# 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 {}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20