Java 调节器与查询

Java 调节器与查询,给定一个时态值(temporal value),用户希望根据自定义逻辑对其进行调整,或检索给定值的相关信息,可以创建 TemporalAdjuster 或规划 TemporalQuery 接口。TemporalAdjusterTemporalQuery 接口中的类不仅提供使用 Date-Time API 中各种类的有趣方式,也提供有用的内置方法以实现用户自定义的方法。

Java 调节器与查询 问题描述

给定一个时态值(temporal value),用户希望根据自定义逻辑对其进行调整,或检索给定值的相关信息。

Java 调节器与查询 解决方案

创建 TemporalAdjuster 或规划 TemporalQuery 接口。

Java 调节器与查询 具体实例

TemporalAdjusterTemporalQuery 接口中的类不仅提供使用 Date-Time API 中各种类的有趣方式,也提供有用的内置方法以实现用户自定义的方法。本范例将对此做讨论。

1.TemporalAdjuster的应用
TemporalAdjuster 接口定义了一个名为 adjustInto 的方法,它传入 Temporal 值作为参数,并返回调整后的值。而 TemporalAdjusters 类包括一系列用作静态方法的调节器(adjuster),或许能为开发带来一定便利。
LocalDateTime 类为例,我们可以通过时态对象(temporal object)上的 with 方法使用 TemporalAdjuster

LocalDateTime with(TemporalAdjuster adjuster)

虽然也可以使用 TemporalAdjuster 接口定义的 adjustInto 方法,但 with 方法应作为首选。
我们首先讨论 TemporalAdjusters 类,它定义了多种便利的方法:

static TemporalAdjuster firstDayOfNextMonth()
static TemporalAdjuster firstDayOfNextYear()
static TemporalAdjuster firstDayOfYear()
 
static TemporalAdjuster firstInMonth(DayOfWeek dayOfWeek)
static TemporalAdjuster lastDayOfMonth()
static TemporalAdjuster lastDayOfYear()
static TemporalAdjuster lastInMonth(DayOfWeek dayOfWeek)
 
static TemporalAdjuster next(DayOfWeek dayOfWeek)
static TemporalAdjuster nextOrSame(DayOfWeek dayOfWeek)
static TemporalAdjuster previous(DayOfWeek dayOfWeek)
static TemporalAdjuster previousOrSame(DayOfWeek dayOfWeek)

例 8-10 的用例显示了上述方法在实际开发中的应用。

例 8-10 TemporalAdjusters 类定义的部分静态方法

@Test
public void adjusters() throws Exception {
    LocalDateTime start = LocalDateTime.of(2017, Month.FEBRUARY, 2, 11, 30);
    LocalDateTime end = start.with(TemporalAdjusters.firstDayOfNextMonth());
    assertEquals("2017-03-01T11:30", end.toString());
 
    end = start.with(TemporalAdjusters.next(DayOfWeek.THURSDAY));
    assertEquals("2017-02-09T11:30", end.toString());
 
    end = start.with(TemporalAdjusters.previousOrSame(DayOfWeek.THURSDAY));
    assertEquals("2017-02-02T11:30", end.toString());
}

有趣之处在于编写自定义调节器。TemporalAdjuster 是一个函数式接口,所包含的单一抽象方法为:

Temporal adjustInto(Temporal temporal)

在讨论时态调节器(temporal adjuster)时,Java 官方教程以 PaydayAdjuster 类为例演示了自定义调节器的应用:假设员工在一个月中领取两次工资,且发薪日是每月 15 日和最后一天;如果某个发薪日为周末,则提前到周五。
为便于参考,例 8-11 完整复制了这个示例的代码。请注意,adjustInto 方法已被添加到实现 TemporalAdjuster 接口的 PaydayAdjuster 类中。

例 8-11 PaydayAdjuster 类(取自 Java 官方教程)

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalAdjusters;
 
public class PaydayAdjuster implements TemporalAdjuster {
    public Temporal adjustInto(Temporal input) {
        LocalDate date = LocalDate.from(input);  ➊
        int day;
        if (date.getDayOfMonth() < 15) {
            day = 15;
        } else {
            day = date.with(TemporalAdjusters.lastDayOfMonth())
                      .getDayOfMonth();
        }
        date = date.withDayOfMonth(day);
        if (date.getDayOfWeek() == DayOfWeek.SATURDAY ||
                date.getDayOfWeek() == DayOfWeek.SUNDAY) {
            date = date.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
        }
 
        return input.with(date);
    }
}

from 方法可以将任何时态对象转换为 LocalDate
以 2017 年 7 月为例运行程序,其中 7 月 15 日是周六,7 月 31 日是周一。例 8-12 的测试显示,调节器可以正确处理 2017 年 7 月的发薪日。

例 8-12 测试调节器

@Test
public void payDay() throws Exception {
  TemporalAdjuster adjuster = new PaydayAdjuster();
  IntStream.rangeClosed(1, 14)
           .mapToObj(day -> LocalDate.of(2017, Month.JULY, day))
           .forEach(date ->
               assertEquals(14, date.with(adjuster).getDayOfMonth()));
 
IntStream.rangeClosed(15, 31)
         .mapToObj(day -> LocalDate.of(2017, Month.JULY, day))
         .forEach(date ->
             assertEquals(31, date.with(adjuster).getDayOfMonth()));
}

虽然上述程序可以运行,但仍然存在改进的空间。首先,在 Java 8 之前,如果不使用其他机制就无法创建日期流(例如,本例需要计算天数)。这种情况在 Java 9 中得以改变。Java 9 新增了一种返回日期流的方法 datesUntil,详细讨论请参见范例日期范围
其次,为实现 TemporalAdjuster 接口,程序创建了 PaydayAdjuster 类。由于 TemporalAdjuster 属于函数式接口,不妨改为提供 lambda 表达式或方法引用作为实现。
如例 8-13 所示,我们创建一个名为 Adjusters 的工具类,它包括进行各种操作所需的静态方法。

例 8-13 工具类 Adjusters

public class Adjusters {                                 ➊
    public static Temporal adjustInto(Temporal input) {  ➋
        LocalDate date = LocalDate.from(input);
        // 与例8-11的实现相同
        return input.with(date);
    }
}

❶ 不实现 TemporalAdjuster 接口
❷ 静态方法无须实例化
重写后的测试如例 8-14 所示。

例 8-14 测试调节器(使用方法引用)

@Test
public void payDayWithMethodRef() throws Exception {
    IntStream.rangeClosed(1, 14)
        .mapToObj(day -> LocalDate.of(2017, Month.JULY, day))
        .forEach(date ->
              assertEquals(14,
                  date.with(Adjusters::adjustInto).getDayOfMonth())); ➊
 
    IntStream.rangeClosed(15, 31)
        .mapToObj(day -> LocalDate.of(2017, Month.JULY, day))
        .forEach(date ->
              assertEquals(31,
                  date.with(Adjusters::adjustInto).getDayOfMonth()));
}

adjustInto 的方法引用
如果存在多个时态调节器,上述方案可能更为通用。

2.TemporalQuery的应用
TemporalQuery 接口用作时态对象中 query 方法的参数。以 LocalDate 类为例,query 方法的签名如下:

<R> R query(TemporalQuery<R> query)

query 方法调用 TemporalQuery.queryFrom(TemporalAccessor) 方法(传入 this 作为参数),并返回所需的查询。TemporalAccessor 接口定义的所有方法均可用于查询操作。
Date-Time API 还包括一个名为 TemporalQueries 的类,它定义了许多常见查询的常量:

static TemporalQuery<Chronology>   chronology()
static TemporalQuery<LocalDate>    localDate()
static TemporalQuery<LocalTime>    localTime()
static TemporalQuery<ZoneOffset>   offset()
static TemporalQuery<TemporalUnit> precision()
static TemporalQuery<ZoneId>       zone()
static TemporalQuery<ZoneId>       zoneId()

例 8-15 的简单测试展示了部分方法的应用。

例 8-15 TemporalQueries 类定义的部分方法

@Test
public void queries() throws Exception {
    assertEquals(ChronoUnit.DAYS,
        LocalDate.now().query(TemporalQueries.precision()));
    assertEquals(ChronoUnit.NANOS,
        LocalTime.now().query(TemporalQueries.precision()));
    assertEquals(ZoneId.systemDefault(),
        ZonedDateTime.now().query(TemporalQueries.zone()));
    assertEquals(ZoneId.systemDefault(),
        ZonedDateTime.now().query(TemporalQueries.zoneId()));
}

TemporalAdjuster 接口类似,有趣之处在于编写自定义查询。TemporalQuery 接口包含的单一抽象方法为:

R queryFrom(TemporalAccessor temporal)

如果给定参数 TemporalAccessor,我们可以编写一个名为 daysUntilPirateDay 的方法,以计算指定日期与国际海盗模仿日(International Talk Like A Pirate Day,9 月 19 日)5 之间的天数,如例 8-16 所示。

例 8-16 计算指定日期与国际海盗模仿日之间的天数

private long daysUntilPirateDay(TemporalAccessor temporal) {
    int day = temporal.get(ChronoField.DAY_OF_MONTH);
    int month = temporal.get(ChronoField.MONTH_OF_YEAR);
    int year = temporal.get(ChronoField.YEAR);
    LocalDate date = LocalDate.of(year, month, day);
    LocalDate tlapd = LocalDate.of(year, Month.SEPTEMBER, 19);
    if (date.isAfter(tlapd)) {
        tlapd = tlapd.plusYears(1);
    }
 
    return ChronoUnit.DAYS.between(date, tlapd);
}

由于 daysUntilPirateDay 方法的签名与 TemporalQuery 接口包含的单一抽象方法 queryFrom 相互兼容,可以通过方法引用来调用,如例 8-17 所示。

例 8-17 通过方法引用使用 TemporalQuery

@Test
public void pirateDay() throws Exception {
    IntStream.range(10, 19)
             .mapToObj(n -> LocalDate.of(2017, Month.SEPTEMBER, n))
             .forEach(date ->
                assertTrue(date.query(this::daysUntilPirateDay) <= 9));
    IntStream.rangeClosed(20, 30)
             .mapToObj(n -> LocalDate.of(2017, Month.SEPTEMBER, n))
             .forEach(date -> {
                Long days = date.query(this::daysUntilPirateDay);
                assertTrue(days >= 354 && days < 365);
            });
}

上述方案也可用于创建自定义查询。

5例如,“喂,伙计!我打算把你加入我的 LinkedIn 社交网络。”

赞(0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

Java 实例