【问题标题】:How to make a method generic to work with LocalDate and LocalDateTime?如何使方法通用以与 LocalDate 和 LocalDateTime 一起使用?
【发布时间】:2017-11-25 10:16:51
【问题描述】:

我有一个Java方法如下:

private static boolean isDateBetweenRange(DataSet obj, MyClass dataSource, ConditionContext context) {
    FilterContext fc = dataSource.getData();

    LocalDate dateFieldToCheck = obj.getDate(fc.getDateField()).toInstant()
                                   .atZone(ZoneId.systemDefault()).toLocalDate();
    LocalDate minDate = fc.getMinDateValue();
    LocalDate maxDate = fc.getMaxDateValue();

    if (minDate == null || maxDate == null) {
        minDate = context.getStartDate().toInstant().atZone(ZoneId.systemDefault())
                    .toLocalDate();
        maxDate = context.getEndDate().toInstant().atZone(ZoneId.systemDefault())
                    .toLocalDate();
    }

    boolean result = (dateFieldToCheck.isAfter(minDate) || dateFieldToCheck.isEqual(minDate))
            && (dateFieldToCheck.isBefore(maxDate) || dateFieldToCheck.isEqual(maxDate));

    return result;
}

我也想为LocalDateTime 做同样的逻辑。如果我重载该方法,LocalDateTime 的代码将完全相同。

如何使用泛型或任何其他机制使该方法泛型以与LocalDateLocalDateTime 一起使用?

如何根据我的类型将context.getXXXDate()... toLocalDate()toLocalDateTime() 设为通用代码?

【问题讨论】:

  • 首先我不认为你可以,或者至少不完全,尽管可以排除共享逻辑。其次,不是很清楚。你从哪里得到LocalDateLocalDateTimefc.getMinDateValue() 也可以返回吗?如果是,它声明的返回类型是什么?
  • 顺便说一句,有一个令人讨厌的极端情况:如果有人在你的方法运行时更改了你的 JVM 的时区设置,你会得到令人惊讶的结果。我建议您只调用一次ZoneId.systemDefault(),并将返回的区域保存在一个变量中,以便您每次需要时使用它。这样您就可以确保在所有 atZone() 调用中使用相同的区域。

标签: java-8 polymorphism java-time jsr310


【解决方案1】:

嗯...LocalDateTimeLocalDateLocalTime 的不可变组合。它具有 toLocalDate()toLocalTime() 方法,您可以使用它们“分解它”。反之亦然 - 您也可以很容易地创建合成 - 请参阅 LocalDate.atTime(LocalTime)

如果您在逻辑中考虑时间,您提取的 API-s 应该接受LocalDateTime,这对我来说似乎很自然。在您出于某种原因想要使用LocalDate-s 提供此API 的可疑情况下:) 您可以明确使用,例如LocalDate.atStartOfDay。像这样的东西:

boolean api(LocalDate aDate, LocalDate anotherDate)
{
    return this.api(aDate.atStartOfDay(), anotherDate.atStartOfDay());
}

boolean api(LocalDateTime aDateTime, LocalDateTime anotherDateTime)
{
    return ...;
}

虽然看起来有点难看,但仍然可以很好地工作,不是吗?

【讨论】:

    【解决方案2】:

    TemporalAccessor 接口可用于执行此操作。但是请注意,TemporalAccessor 是一个高级接口,不应在低级实用程序代码之外使用。

    boolean api(TemporalAccessor temporal1, TemporalAccessor temporal2) {
      LocalDate date1 = LocalDate.from(temporal1);
      LocalDate date2 = LocalDate.from(temporal2);
      return ...;
    }
    

    此代码现在将接受 LocalDateLocalDateTimeOffsetDateTimeZonedDateTime

    如 cmets 中所述,在一个业务逻辑中仅调用一次 ZoneId.systemDefault() 至关重要,因为值可能会发生变化。

    【讨论】:

      【解决方案3】:

      你可以让你的方法接收一个java.time.temporal.Temporal作为参数(或者一个TemporalAccessor作为suggested by @JodaStephen)。此代码适用于两者。

      我必须承认我更愿意像@Lachezar Balev's answer 建议的那样做,但无论如何,这里有一个替代方案。

      Temporal 可以是任何类型(LocalDateLocalDateTimeZonedDateTime 等),但您只需要 LocalDateLocalDateTime,我正在执行以下操作:

      • 首先尝试创建LocalDateTime:它需要比LocalDate(小时/分钟/秒)更多的字段,所以如果可以创建,它是首选类型
      • 如果无法创建,我会捕获相应的异常并尝试创建LocalDate

      另一个细节是我也简化了你的比较:

      dateFieldToCheck.isAfter(minDate) || dateFieldToCheck.isEqual(minDate)
      

      相当于:

      ! dateFieldToCheck.isBefore(minDate)
      

      maxDate比较时也做了类似的简化:

      public boolean check(Temporal dateFieldToCheck, Temporal minDate, Temporal maxDate) {
          // try to get as LocalDateTime
          try {
              LocalDateTime dtCheck = LocalDateTime.from(dateFieldToCheck);
              LocalDateTime dtMin = LocalDateTime.from(minDate);
              LocalDateTime dtMax = LocalDateTime.from(maxDate);
      
              return (!dtCheck.isBefore(dtMin)) && (!dtCheck.isAfter(dtMax));
          } catch (DateTimeException e) {
              // exception: one of the Temporal objects above doesn't have all fields required by a LocalDateTime
              // trying a LocaDate instead
              LocalDate dCheck = LocalDate.from(dateFieldToCheck);
              LocalDate dMin = LocalDate.from(minDate);
              LocalDate dMax = LocalDate.from(maxDate);
      
              return (!dCheck.isBefore(dMin)) && (!dCheck.isAfter(dMax));
          }
      }
      

      现在您可以使用LocalDateLocalDateTime 调用此方法:

      check(LocalDate.of(2017, 6, 19), LocalDate.of(2017, 6, 18), LocalDate.of(2017, 6, 29)); // true
      check(LocalDate.of(2017, 6, 17), LocalDate.of(2017, 6, 18), LocalDate.of(2017, 6, 29)); // false
      check(LocalDateTime.of(2017, 6, 29, 9, 0), LocalDateTime.of(2017, 6, 29, 8, 0), LocalDateTime.of(2017, 6, 29, 11, 0)); // true
      check(LocalDateTime.of(2017, 6, 29, 7, 0), LocalDateTime.of(2017, 6, 29, 8, 0), LocalDateTime.of(2017, 6, 29, 11, 0)); // false
      

      如果需要,您可以扩展代码以识别其他类型(例如LocalTime)。

      您还可以添加另一个参数来指示将使用的类型,如下所示:

      public boolean check(Temporal dateFieldToCheck, Temporal minDate, Temporal maxDate, int type) {
          switch (type) {
              case 1:
                  // try to get as LocalDateTime
                  LocalDateTime dtCheck = LocalDateTime.from(dateFieldToCheck);
                  LocalDateTime dtMin = LocalDateTime.from(minDate);
                  LocalDateTime dtMax = LocalDateTime.from(maxDate);
      
                  return (!dtCheck.isBefore(dtMin)) && (!dtCheck.isAfter(dtMax));
              case 2:
                  // trying a LocaDate instead
                  LocalDate dCheck = LocalDate.from(dateFieldToCheck);
                  LocalDate dMin = LocalDate.from(minDate);
                  LocalDate dMax = LocalDate.from(maxDate);
      
                  return (!dCheck.isBefore(dMin)) && (!dCheck.isAfter(dMax));
              // ... and so on
          }
      
          // invalid type, return false?
          return false;
      }
      

      因此您可以使用ZonedDateTime 调用它并强制它使用LocalDate(然后您可以调整您的代码并使用您从atZone(ZoneId.systemDefault()) 获得的对象调用此方法):

      ZonedDateTime z1 = obj.getDate(fc.getDateField()).toInstant().atZone(ZoneId.systemDefault());
      ZonedDateTime z2 = context.getStartDate().toInstant().atZone(ZoneId.systemDefault());
      ZonedDateTime z3 = context.getEndDate().toInstant().atZone(ZoneId.systemDefault());
      check(z1, z2, z3, 2);
      

      此代码也适用于不同的类型:

      ZonedDateTime z1 = ...
      LocalDate d = ...
      LocalDateTime dt = ...
      // converts all to LocalDate
      System.out.println(check(z1, dt, d, 2));
      

      PS:当然你可以创建一个Enum 而不是int 来定义类型。或者使用Class<? extends Temporal>直接指明类型:

      public boolean check(Temporal dateFieldToCheck, Temporal minDate, Temporal maxDate, Class<? extends Temporal> type) {
          if (type == LocalDate.class) {
              // code for LocalDate (use LocalDate.from() as above)
          }
          if (type == LocalDateTime.class) {
              // code for LocalDateTime (use LocalDateTime.from() as above)
          }
      }
      
      // convert all objects to LocalDate
      check(z1, dt, d, LocalDate.class);
      

      【讨论】:

        猜你喜欢
        • 2023-03-23
        • 2022-01-06
        • 1970-01-01
        • 2019-04-23
        • 2015-03-01
        • 2019-11-18
        • 1970-01-01
        • 2021-05-25
        相关资源
        最近更新 更多