支持Java中的协变类型转换

时间:2009-10-14 06:18:06

标签: java generics types

Java类型系统仅支持不变类型。因此List<String>不是List<Object>List<String>不是List<Object>,因为将Integer插入List<String>无效。但是,有些类型的协变类型转换是有效的。

鉴于A,B和Producer类:

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

可以定义协变类型Producer的强制转换:

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

此方法支持从Producer<A>投射到Producer<Object>并阻止Producer<A>Producer<B>之类的无效投射:

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

我的问题是我无法执行从Producer<Producer<A>>Producer<Producer<Object>>的演员表。

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

有没有办法说服Java类型系统在用户代码中没有警告的情况下执行这种有效的类型转换?

5 个答案:

答案 0 :(得分:8)

您尚未发布Producer的代码,但是根据名称和断言它应该是协变的,也许您现在所说的位置:

Producer<Foo>

你应该说:

Producer<? extends Foo>

如果Java会自动意识到通用接口等同于其通配符表单(例如IteratorIterable也是安全协变的),那将是很好的,但至少现在,它没有。

答案 1 :(得分:2)

好吧,你可以通过原始类型进行操作

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

但这很难,理论上也不正确。您应该使用Producer<Producer<?>>(或Producer<? extends Producer<?>>),但如果确实无法,我建议您改为使用包装。

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());
    }
};

稍微增加一些开销,但是这样你就不必强奸类型系统了,真的不起作用的东西看起来也不像。


修改:您还可以针对cast方法做一个特例:

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

但是,如果您要将Producer<Producer<Producer<String>>>转换为Producer<Producer<Producer<Object>>>,则必须为此添加其他方法,依此类推。由于严格来说这是类型系统的错误使用,因此以这种方式工作不方便并不奇怪。

答案 2 :(得分:1)

Java没有提供任何方法来指定泛型类型应该是协变的(或逆变的)。在您的情况下,如果我可以从Producer&lt; Producer&lt; A&gt;&gt;转换为Producer&lt; Producer&lt; A&gt;&gt;,那么我可以这样做:

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

但是,当然,objProducerProducer实际上正在制作Producer&lt; A&gt;对象。 Java无法将这些转换为Producer&lt; Object&gt;自动,就是这样。您的显式强制转换静态方法有效,因为您需要&lt;?延伸T>。泛型不会相互扩展。我想你想要的是与泛型系统集成的隐式转换,这是我们所拥有的一个很长的飞跃。

编辑:澄清,是的,在您的情况下,处理制作人&lt; A&gt;是安全的。作为生产者&lt; Object&gt;,但这是泛型系统无法获得的知识。

答案 3 :(得分:1)

你在你的实现中类型类不是强制转换,它只是一种隐藏编译器警告的方便方法(以及一种欺骗同事的方便方法)。因此,我宁愿建议将Producer的实现改为这样的(如果可能的话),而不是“生成”你的制作人:

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);

注意:它可能对您的确切问题没有帮助,但可能会指向正确的方向。

答案 4 :(得分:1)

这应该是在Java中的使用站点使用通配符处理的。

但是,您可以使用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();
        }
    };
}