Why does Temporal not extend Comparable in Java 8 jsr310
Asked Answered
E

4

14

The documentation for java.time.temporal.Temporal contains the following note:

Implementation Requirements: [...] All implementations must be Comparable.

Why does Temporal not just extend Comparable?

Background: I want to work with comparable temporals (not with subtypes like LocalDateTime etc.) and have to resort to a somewhat illegible type <T extends Temporal & Comparable<T>> which also messes up NetBeans' auto-complete feature.

Edit: I want to implement a temporal interval. The obvious implementations for contains(Interval i), contains(Temporal t), overlaps(...), adjoins(...) etc. use Comparable::compareTo(Comparable c) to compare the start and end points, but for interoperability (toDuration(), parse(CharSequence cs)) I need e.g. Duration::between(Temporal s, Temporal e) or SubtypeOfTemporal::parse(CharSequence cs) (yielding Temporal).

Encyclopedic answered 26/5, 2014 at 13:10 Comment(4)
I want to work with comparable temporals (not with subtypes like LocalDateTime etc.): could you explain more specifically?Bibliotherapy
If it implemented Comparable<Temporal>, every suclass instance would have to be comparable any other subclass instance. And comparing an Instant with a LocalDate doesn't make sense. Given that the contract mandates that they are comparable, you can cast T to Comparable<T> and safely ignore the compiler warning.Oro
@assylias: right. What started as a comment ended as an answer. I reposted it as an answer.Oro
I don't know why you need to work with Temporal. Really, the JSR-310-designers want you to work with concrete types only. On that level Comparable is always implemented (but not always with self-referencing Generics, see class LocalDate!).Drumfire
D
5

The answer of @JBNizet was very obscure for me on first glance because he advises to do a simple type-cast to Comparable (ignoring compiler warning) and generally I would prefer code without any type-casts or warnings (they are not out there just for fun), but first now I find the time to investigate the whole thing more carefully. Let's consider following simple interval example:

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

On that base (preferred by OP as far as I have understood him) it is logical that the compiler will reject first line in following code:

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

The reason is that LocalDate does not implement Comparable<LocalDate> but Comparable<ChronoLocalDate>. So if we go instead with the approach of @JBNizet and write with the simplified upper bound for T (just Temporal) and then use type-erasure at runtime:

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

This code compiles. And at runtime:

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

All fine? No. A negative example demonstrates the unsafety of the new generic FlexInterval-signature (the compiler warning has its reason). If we just choose an abstract type at runtime (some users might do this in "universal" (bad) helper classes):

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()));

... then the code compiles again, but we get:

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)

Conclusion:

The type-safety strongly requires self-referencing generics (not supported by JSR-310) AND concrete types. Since JSR-310-team has intentionally avoided generics where ever they can users willing to use JSR-310 should respect this design decision and also avoid generics in their application code. Users are best advised if they just use concrete final types, no general-purpose generified classes (which can not be completely safe).

Most important lesson: Avoid the interface Temporal in any application code.

To be noted: The hostile attitude over for generics is not my personal view. I myself can well imagine a time library which is generified. But this is another subject we don't speak about in this topic.

Drumfire answered 28/5, 2014 at 11:13 Comment(0)
O
10

If it implemented Comparable<Temporal>, every suclass instance would have to be comparable with any other subclass instance. And comparing an Instant with a LocalDate, for example, doesn't make sense.

Given that the contract mandates that they are comparable, you can cast T to Comparable<T> and safely ignore the compiler warning.

Oro answered 26/5, 2014 at 14:17 Comment(4)
No, you cannot safely cast every temporal type T to Comparable<T>, the class LocalDate is a counter example (Comparable<ChronoLocalDate>). Generics are not well supported in JSR-310 (by intention).Drumfire
@MenoHochschild but thanks to type erasure, that won't cause any problem. See gist.github.com/jnizet/a5bac068123afdc36421 for an example.Oro
You should test it with different concrete types like LocalDate and MinguoDate.Drumfire
Do it, and you'll see that it's OK.Oro
K
6

Attempts were made to implement Comparable, but because Java does not have self-type generics, it was necessary to have Temporal generified by its subtype (like Enum). In practice, this was not a good trade off, as in 95%+ usages of Temporal, the generified parameter would be unknown and thus Temporal<?>. Since the only generified solution was verbose and impractical to most users, it was not retained.

As JB Nizet's answer says, you can just cast to Comparable in most cases. Providing the two inputs to compareTo are of the same concrete type, you should see no problems.

On intervals, my suspicion is that a LocalDateRange, an InstantInterval and a LocalTimeInterval have less in common than might be imagined and a generified solution is probably worse than coding three separate classes. Remember that is OK to choose against using generics providing the the trade-offs have been considered.

Karwan answered 26/5, 2014 at 18:46 Comment(1)
What possible problems do you see with a generified solution? With the somewhat cluttered type restrictions mentioned above I get a usable interval implementation ... I do not want to put too much functionality into it; only what I wrote in my question ...Saprolite
D
5

The answer of @JBNizet was very obscure for me on first glance because he advises to do a simple type-cast to Comparable (ignoring compiler warning) and generally I would prefer code without any type-casts or warnings (they are not out there just for fun), but first now I find the time to investigate the whole thing more carefully. Let's consider following simple interval example:

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

On that base (preferred by OP as far as I have understood him) it is logical that the compiler will reject first line in following code:

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

The reason is that LocalDate does not implement Comparable<LocalDate> but Comparable<ChronoLocalDate>. So if we go instead with the approach of @JBNizet and write with the simplified upper bound for T (just Temporal) and then use type-erasure at runtime:

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

This code compiles. And at runtime:

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

All fine? No. A negative example demonstrates the unsafety of the new generic FlexInterval-signature (the compiler warning has its reason). If we just choose an abstract type at runtime (some users might do this in "universal" (bad) helper classes):

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()));

... then the code compiles again, but we get:

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)

Conclusion:

The type-safety strongly requires self-referencing generics (not supported by JSR-310) AND concrete types. Since JSR-310-team has intentionally avoided generics where ever they can users willing to use JSR-310 should respect this design decision and also avoid generics in their application code. Users are best advised if they just use concrete final types, no general-purpose generified classes (which can not be completely safe).

Most important lesson: Avoid the interface Temporal in any application code.

To be noted: The hostile attitude over for generics is not my personal view. I myself can well imagine a time library which is generified. But this is another subject we don't speak about in this topic.

Drumfire answered 28/5, 2014 at 11:13 Comment(0)
T
0

I solved it this way:

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

The class then provides an Iterator<T> and Spliterator<T>, in addition to stream() and parallelStream() methods. I require type casts to (T) when I'm using the plus() method, but the java.time package does that as well, so hey.

It can be found here: https://github.com/SeverityOne/time-ext

Still in its infancy at the time of writing, not much in terms of unit tests, and no checking against endless loops.

Tracytrade answered 3/4, 2019 at 20:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.