4

I'm trying to extends TemporalAdjuster so that one looks like,

public interface TypedTemporalAdjuster<T extends Temporal & Comparable<? super T>> {

    T adjustInto(T temporal);
}

When I tried to directly extends the base interface,

public interface TypedTemporalAdjuster<T extends Temporal & Comparable<? super T>>
        extends TemporalAdjuster {

    T adjustInto(T temporal);
}

I got an error.

...java: name clash: ... have the same erasure, yet neither overrides the other

Is there any way to do this?

So far, I did.

public interface TypedTemporalAdjuster<T extends Temporal & Comparable<? super T>> { //extends TemporalAdjuster {

    static <T extends Temporal & Comparable<? super T>> TypedTemporalAdjuster<T> of(
            final Class<T> temporalClass, final TemporalAdjuster temporalAdjuster) {
        return temporal -> temporalClass.cast(temporalAdjuster.adjustInto(temporalClass.cast(temporal)));
    }

    T adjustInto(T temporal);
}
Jin Kwon
  • 20,295
  • 14
  • 115
  • 184
  • 1
    I think a new interface is the way to go here, right as you did. – Glains Aug 30 '18 at 05:39
  • 2
    You can make a return type more specific, but if you make a parameter type more specific then you aren't overriding the method anymore. – E.M. Aug 30 '18 at 05:39
  • 1
    Could also consider overriding inherited method, then adding a new abstract one to which the default delegates: `@Override default Temporal adjustInto(Temporal temporal) { return adjustInto2((T) temporal); } T adjustInto2(T temporal);`. This will still be a functional interface. – ernest_k Aug 30 '18 at 05:46

1 Answers1

7

You can not override a method with more restrictive parameters, i.e. T adjustInto(T temporal); does not override Temporal adjustInto(Temporal temporal); as the parameter type T is more restrictive than Temporal. So you have two methods with the name adjustInto now, but due to type erasure, the parameter types are identical on the byte code level, as T extends Temporal & Comparable<? super T> gets erased to Temporal.

You could fix that by changing the declaration to

public interface TypedTemporalAdjuster<T extends Comparable<? super T> & Temporal>
extends TemporalAdjuster {
    T adjustInto(T temporal);
}

as then, the semantically identical T extends Comparable<? super T> & Temporal gets erased to Comparable instead of Temporal. You could also use T extends Object & Comparable<? super T> & Temporal which gets erased to Object (usually, such knowledge is only relevant when you need compatibility with pre-Generics code).

However, the fundamental problem remains, adjustInto(T temporal); does not override adjustInto(Temporal temporal); as T is a more restrictive parameter, so now, the interface is not a functional interface anymore, as it has two abstract methods.

A sub-interface of TemporalAdjuster must provide all of its operatation, including an adjustInto accepting any Temporal. So you can only do

public interface TypedTemporalAdjuster<T extends Temporal & Comparable<? super T>>
extends TemporalAdjuster {

    static <T extends Temporal & Comparable<? super T>> TypedTemporalAdjuster<T> of(
            final Class<T> temporalClass, final TemporalAdjuster temporalAdjuster) {
        return temporal -> temporalClass.cast(temporalAdjuster.adjustInto(temporal));
    }

    @Override T adjustInto(Temporal temporal);
}

However, such wrapped adjusters can not ensure correct arguments and only hide the type cast which still may fail at runtime. But it looks like you are trying to solve a non-existent problem here, as you can simply use the with method on the temporal, to get a type safe operation, e.g.

TemporalAdjuster a = TemporalAdjusters.lastDayOfMonth();

LocalDate     date1     = LocalDate.now(),     date2     = date1.with(a);
LocalDateTime dateTime1 = LocalDateTime.now(), dateTime2 = dateTime1.with(a);
ZonedDateTime zoned1    = ZonedDateTime.now(), zoned2    = zoned1.with(a);

That’s even more powerful than your wrapper, as when you do, e.g.

TemporalAdjuster a = TemporalAdjusters.ofDateAdjuster(date -> date.plusDays(1));

ZonedDateTime zoned1 = ZonedDateTime.now(), zoned2 = zoned1.with(a);

You define an operation only once, in terms of a LocalDate manipulation, while it works for other temporals by converting them on-the-fly, rather than casting.

Holger
  • 285,553
  • 42
  • 434
  • 765