Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I extend a non generic interface into a generic one?

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);
}
like image 462
Jin Kwon Avatar asked Mar 05 '23 13:03

Jin Kwon


1 Answers

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.

like image 145
Holger Avatar answered Mar 16 '23 00:03

Holger