开发者

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>开发者_StackOverflow中文版 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?


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.


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.


Java does not provide any way to specify that a generic type should be covariant (or contravariant). In your case, if I can cast from a Producer<Producer<A>>, to a Producer<Producer<A>>, then I can do this:

Producer<Producer<Object>> objProducerProducer = cast(Producer<Producer<A>>);
Producer<Object> objProducer = objProducerProducer.produce()

But of course, objProducerProducer is actually producing Producer<A> objects. Java can't cast these to Producer<Object> automatically, that's just how it goes. Your explicit cast static method works because you are requiring <? extends T>. The generics don't extend each other. I guess what you want is implicit conversions that are integrated with the generics system, which is a long leap from what we have.

Edit: to clarify, yes, in your case, it's safe to treat a Producer<A> as a Producer<Object>, but this is knowledge that's not available to the generics system.


What you implemented in you Types class isn't a cast, it's only a convenient way to hide compiler warnings (and a convenient way to fool co-workers I guess). Therefore, instead of "casting" your producer, I'd rather suggest to change the implementation of Producer to something like this (if possible):

class Producer {
  <T> T produce(Class<T> cls) {
    Object o;

    // do something, e.g.
    // o = cls.newInstance();

    // perfectly type safe cast
    return cls.cast(o);
  }
}

Producer p = new Producer();
A a = p.produce(A.class);

Note: It might not help you with your exact problem, but maybe it points you towards the right direction.


This is something that should be handled with wildcards at use site in Java.

However you can do it explicitly with a proxy in Java.

public static <T> Iterator<T> clean(final Iterator<? extends T> orig) {
    return new Iterator<T>() {
        public boolean hasNext() {
             return orig.hasNext();
        }
        public T next() {
             return orig.next();
        }
        public void remove() {
             return orig.remove();
        }
    };
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜