为什么Temporal不能在Java 8 jsr310中扩展Comparable

Ren*_*ené 10 java java-8 java-time

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

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

为什么Temporal不仅仅扩展可比性?

背景:我想使用类似的时间(不是像LocalDateTime等子类型),并且必须采用一种有点难以辨认的类型<T extends Temporal & Comparable<T>>,这也会混淆NetBeans的自动完成功能.

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

JB *_*zet 6

如果实现Comparable<Temporal>,则每个suclass实例都必须与任何其他子类实例相当.例如,将Instant与LocalDate进行比较是没有意义的.

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


Jod*_*hen 5

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

正如JB Nizet的回答所说,你可以Comparable在大多数情况下进行投射.提供两个输入compareTo具有相同的具体类型,您应该看到没有问题.

有一段时间,我怀疑a LocalDateRange,a InstantInterval和a的LocalTimeInterval共同点比想象的要少,而且一般化的解决方案可能比编写三个单独的类更糟糕.请记住,可以选择不使用泛型,只要考虑到权衡.


Men*_*ild 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

原因是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);
  }
}
Run Code Online (Sandbox Code Playgroud)

此代码编译。在运行时:

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

一切都好吗?不是。一个反面例子证明了新的泛型签名的不安全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()));
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

结论:

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

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

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