Reputation: 942
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).
Upvotes: 14
Views: 3596
Reputation: 2691
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.
Upvotes: 0
Reputation: 44061
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.
Upvotes: 5
Reputation: 63385
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.
Upvotes: 6
Reputation: 691715
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.
Upvotes: 10