views:

181

answers:

5

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?

+4  A: 

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.

Laurence Gonsalves
Probably shouldn't be automatic. Consider adding a method to a class which made the conversion invalid.
Tom Hawtin - tackline
That's the problem with usage side declaration of variance. If you could state in the Producer interface that it is covariant this could be enforced by the compiler. You could never implement a Producer that does break the contract. With the usage side declaration you hope that all implementations of Producer are covariant. There is no prove and invalid implementations will break the system. But there has to be way to support covariant conversion even if it is as limited as a cast. It is useful from a user perspective.
Thomas Jung
@Thomas - this is the correct answer. If the methods declare their bounded wildcards correctly, everything will "just work". If they don't, then you will and *should* get "unchecked" warnings in your code (which will still function correctly). Note that this answer does **not** require changing the Producer class, only the methods that take one as an argument.
Andrzej Doyle
You have two options, then; wrappers or unchecked casts (as in my post). Java's type system (and probably most Java programmers) wants you to use `Producer<? extends Producer<?>>` rather than `Producer<Producer<Object>>`, though.
gustafc
Tom Hawtin: Yeah, I'm aware of that issue, though I'm not sure if it'd be a problem in practice or not. It certainly seems like a very small problem in interfaces, (as adding methods to an interface already breaks existing code) and I'm not even convinced that it's a bad thing for classes. But anyway, whether it was explicit or implicit, *some* way to avoid having to write "? extends Iterator" everywhere would be nice.
Laurence Gonsalves
Thomas Jung: regarding "There is no [proof an] invalid implementations will break the system". If an implementation of Producer adds methods that are not covariant, that doesn't matter. You won't have access to those methods via the wildcarded class anyway. For example, take ListIterator, a non-covariant extension of Iterator. If I say "give me an Iterator<? extends Shape>" and you give me a ListIterator<Circle> that's okay, because I can't call your add(Shape) method anyway.
Laurence Gonsalves
+1  A: 

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.

Kevin Peterson
+1  A: 

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.

sfussenegger
+2  A: 

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.

gustafc
+1  A: 

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();
        }
    };
}
Tom Hawtin - tackline
You have a very strange idea of better. I hope I never have to touch any of your code.
Tom Hawtin - tackline