14

java.time.temporal.Temporal的文档包含以下注释:

实施要求:[...]所有实施必须是可比较的。

为什么 Temporal 不只是扩展 Comparable?

背景:我想使用可比较的时间(而不是像 LocalDateTime 等子类型),并且不得不求助于一种有些难以辨认的类型<T extends Temporal & Comparable<T>>,这也会破坏 NetBeans 的自动完成功能。

编辑:我想实现一个时间间隔。contains(Interval i)、contains(Temporal t)、overlaps(...)、adjoins(...) 等的明显实现使用 Comparable::compareTo(Comparable c) 来比较起点和终点,但对于互操作性 (toDuration(), parse(CharSequence cs)) 我需要 Duration::between(Temporal s, Temporal e) 或 SubtypeOfTemporal::parse(CharSequence cs) (产生 Temporal)。

4

4 回答 4

10

如果它实现了Comparable<Temporal>,则每个子类实例都必须与任何其他子类实例具有可比性。例如,将 Instant 与 LocalDate 进行比较是没有意义的。

鉴于合同要求它们具有可比性,您可以强制T转换Comparable<T>并安全地忽略编译器警告。

于 2014-05-26T14:17:51.517 回答
6

曾尝试实现Comparable,但由于 Java 没有自类型泛型,因此必须Temporal通过其子类型(如Enum)进行泛型化。在实践中,这不是一个好的权衡,因为在 95%+ 的使用中Temporal,泛化参数将是未知的,因此Temporal<?>. 由于唯一通用的解决方案对大多数用户来说冗长且不切实际,因此没有保留它。

Comparable正如 JB Nizet 的回答所说,在大多数情况下,您可以直接转换为。提供两个输入compareTo是相同的具体类型,您应该看不到任何问题。

每隔一段时间,我怀疑 a LocalDateRange、 anInstantInterval和 aLocalTimeInterval的共同点比想象的要少,通用的解决方案可能比编写三个单独的类更糟糕。请记住,如果考虑了权衡,则可以选择不使用泛型。

于 2014-05-26T18:46:58.070 回答
5

乍一看,@JBNizet 的答案对我来说非常模糊,因为他建议对Comparable(忽略编译器警告)进行简单的类型转换,通常我更喜欢没有任何类型转换或警告的代码(它们不只是为了有趣),但首先现在我有时间更仔细地调查整个事情。让我们考虑以下简单的区间示例:

public class FlexInterval<T extends Temporal & Comparable<T>> {

    private final T from;
    private final T to;

    public FlexInterval(T from, T to) {
        super();
        this.from = from;
        this.to = to;
    }

    public boolean contains(T test) {
        return (this.from.compareTo(test) <= 0) && (this.to.compareTo(test) >= 0);
    }
}

在此基础上(据我了解,OP 首选),编译器将拒绝以下代码中的第一行是合乎逻辑的:

FlexInterval<LocalDate> interval = 
  new FlexInterval<LocalDate>(today, today); // compile-error
System.out.println(interval.contains(LocalDate.of(2013, 4, 1));

原因是LocalDate没有实现Comparable<LocalDate>但是Comparable<ChronoLocalDate>。因此,如果我们改用@JBNizet 的方法并使用 T 的简化上限(只是 Temporal)编写,然后在运行时使用类型擦除:

public class FlexInterval<T extends Temporal> {

  ...

  @SuppressWarnings("unchecked") // code smell!
  public boolean contains(T test) {
    Comparable<T> t1 = (Comparable<T>) this.from;
    Comparable<T> t2 = (Comparable<T>) this.to;
    return (t1.compareTo(test) <= 0) && (t2.compareTo(test) >= 0);
  }
}

此代码编译。在运行时:

FlexInterval<LocalDate> interval = 
  new FlexInterval<LocalDate>(today, today);
System.out.println(interval.contains(LocalDate.of(2013, 4, 1));
// output: false

都好吗?不。一个负面的例子表明了新的通用签名的不安全FlexInterval性(编译器警告有其原因)。如果我们只是在运行时选择一个抽象类型(一些用户可能会在“通用”(坏)辅助类中这样做):

LocalDate today = LocalDate.now();
FlexInterval<Temporal> interval = new FlexInterval<Temporal>(today, today);
System.out.println(interval.contains(LocalDate.of(2013,4,1))); // output: false
System.out.println(interval.contains(LocalTime.now()));

...然后代码再次编译,但我们得到:

Exception in thread "main" java.lang.ClassCastException: java.time.LocalTime can
not be cast to java.time.chrono.ChronoLocalDate
        at java.time.LocalDate.compareTo(LocalDate.java:137)
        at FlexInterval.contains(FlexInterval.java:21)

结论:

类型安全强烈要求自引用泛型(JSR-310 不支持)和具体类型。由于 JSR-310 团队有意尽可能避免使用泛型,因此愿意使用 JSR-310 的用户应该尊重这一设计决策,并在其应用程序代码中避免使用泛型。最好建议用户只使用具体的最终类型,而不使用通用的泛型类(这不是完全安全的)。

最重要的一课:避免Temporal在任何应用程序代码中使用接口。

需要注意的是:对仿制药的敌对态度不是我个人的看法。我自己可以很好地想象一个泛型的时间库。但这是我们在本主题中不讨论的另一个主题。

于 2014-05-28T11:13:38.200 回答
0

我是这样解决的:

public class TemporalRange<T extends Temporal & Comparable<? super T>> implements Iterable<T>

除了和方法之外,该类还提供一个Iterator<T>and 。当我使用该方法时,我需要类型转换,但是包也这样做,所以嘿。Spliterator<T>stream()parallelStream()(T)plus()java.time

可以在这里找到:https ://github.com/SeverityOne/time-ext

在撰写本文时仍处于起步阶段,在单元测试方面并不多,也没有检查无限循环。

于 2019-04-03T20:58:06.467 回答