Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Supporting covariant type conversions in Java

The Java type system supports only invariant types. So a List<String> is not an List<Object>. A List<String> is not a List<Object> as it is not valid to insert an Integer into a List<String>. However, there are types for which such a covariant type conversion is valid.

Given the classes A, B and Producer:

class A{}
class B{}
interface Producer<T> {
    T next();
}

A cast for the covariant type Producer can be defined:

class Types{
    @SuppressWarnings("unchecked")
    public static <T> Producer<T> cast(Producer<? extends T> producer){
        return (Producer<T>) producer;
    }
}

This method supports to cast from Producer<A> to Producer<Object> and prevents invalid casts like Producer<A> to Producer<B>:

Producer<Object> valid = Types.<Object> cast(new Producer<A>());
Producer<A> invalid = Types.<A> cast(new Producer<B>()); //does not compile

My problem is that I cannot perform a cast from Producer<Producer<A>> to Producer<Producer<Object>>.

Producer<Producer<A>> producerOfA = new Producer<Producer<A>>();
Producer<Producer<Object>> producerOfObjects = 
   Types.<Producer<Object>> cast(producerOfA); //does not compile

Is there a way to persuade the Java type system to perform such a valid type conversion without warnings in user code?

like image 248
Thomas Jung Avatar asked Dec 03 '25 00:12

Thomas Jung


2 Answers

You haven't posted the code for Producer, but based on the name and your assertion that it should be covariant, perhaps wherever you currently say:

Producer<Foo>

You should instead say:

Producer<? extends Foo>

It would be nice if Java would automatically realize that a generic interface was equivalent to its wildcarded forms (Iterator and Iterable are also safely covariant, for example), but for now at least, it doesn't.

like image 82
Laurence Gonsalves Avatar answered Dec 05 '25 13:12

Laurence Gonsalves


Well, you could just go via raw types and do

Producer<Producer<String>> spp = ...;
Producer<Producer<Object>> opp = (Producer<Producer<Object>>)(Producer) spp;

But it's fugly and theoretically incorrect. You should use Producer<Producer<?>> (or Producer<? extends Producer<?>>), but if you really can't, I'd advise you to make a wrapper instead.

class CovariantProducer<T> implements Producer<T> {
    private final Producer<? extends T> backing;
    public CovariantProducer(Producer<? extends T> backing) { 
        this.backing = backing; 
    }
    @Override
    public T next(){ return backing.next(); }
}

// Usage:

Producer<String> sp = ...;
Producer<Object> op = new CovariantProducer<Object>(sp);
final Producer<Producer<String>> spp = ...;
Producer<Producer<Object>> opp = new Producer<Producer<Object>>() {
    @Override
    public Producer<Object> next() {
        return new CovariantProducer<Object>(spp.next());
    }
};

Somewhat more overhead, but this way you don't have to rape the type system, and the stuff that really doesn't work doesn't look like it works, either.


Edit: You could also do a special case of your cast method:

@SuppressWarnings("unchecked")
public static <T> Producer<Producer<T>> castMetaProducer(Producer<? extends Producer<? extends T>> producer){
    return (Producer<Producer<T>>) producer;
}

However, if you're gonna want to turn a Producer<Producer<Producer<String>>> into a Producer<Producer<Producer<Object>>>, you'd have to add another method for that, and so on. Since this strictly speaking is incorrect usage of the type system, it's not very strange it's inconvenient to work this way.

like image 34
gustafc Avatar answered Dec 05 '25 12:12

gustafc



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!