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.
Temporal
. Really, the JSR-310-designers want you to work with concrete types only. On that levelComparable
is always implemented (but not always with self-referencing Generics, see classLocalDate
!). – Drumfire