在Java中支持协变类型转换

3
Java类型系统仅支持不变类型。因此,List<String>不是List<Object>。由于在List<String>中插入一个Integer是无效的,所以List<String>也不是List<Object>。但是,有些类型可以进行协变类型转换。
给定类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类型系统在用户代码中执行这样有效的类型转换,而不会出现警告?

我已经更正了Producer类型。它是一个可以在用户代码中实现的接口。因此,更改实现Producer的类不是一个选项。 - Thomas Jung
5个回答

8

您还没有发布 Producer 的代码,但根据名称和您声称它应该是协变的,也许您当前在哪里说:

Producer<Foo>

你应该改为说:
Producer<? extends Foo>

如果Java能够自动意识到泛型接口与其通配符形式等效(例如,IteratorIterable也是安全协变的),那将是很好的,但目前至少还没有实现。


可能不应该自动进行。考虑向类中添加一个方法,使转换无效。 - Tom Hawtin - tackline
这就是使用方声明变量的问题所在。如果您可以在生产者接口中声明它是协变的,那么编译器就可以强制执行这一点。您永远无法实现违反合同的生产者。使用方声明希望所有生产者的实现都是协变的,但没有证明,无效的实现将破坏系统。但必须有一种方法来支持协变转换,即使它像转换一样受限。从用户角度来看,这非常有用。 - Thomas Jung
Tom Hawtin:是的,我知道这个问题,尽管我不确定在实践中是否会成为一个问题。对于接口来说,这似乎只是一个很小的问题(因为向接口添加方法已经会导致现有代码失效),而且我甚至不认为这对类来说是一件坏事。但无论如何,无论是显式还是隐式,有某种方式可以避免在每个地方都写“? extends Iterator”将是很好的。 - Laurence Gonsalves
Thomas Jung:关于“没有证据表明无效的实现会破坏系统”的问题。如果Producer的实现添加了不是协变的方法,那也没关系。你无论如何都无法通过通配符类访问这些方法。例如,ListIterator就是一个非协变的Iterator扩展。如果我说“给我一个Iterator<? extends Shape>”,而你给我一个ListIterator<Circle>,那没关系,因为我无论如何都不能调用你的add(Shape)方法。 - Laurence Gonsalves
来自未来的问候。有趣的是,这是 Kotlin 类型系统与 Java 不同之处之一:Kotlin 支持声明点变异,它允许您在类/接口声明中声明变异。 - Laurence Gonsalves
显示剩余2条评论

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>>>,你需要为此添加另一个方法,以此类推。由于这严格来说是类型系统的不正确使用,因此以这种方式工作并不方便。


1

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

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

当然,objProducerProducer实际上是生成Producer<A>对象。Java无法自动将它们转换为Producer<Object>,这就是事实。您的显式转换静态方法之所以有效,是因为您要求<? extends T>。泛型不会相互扩展。我想你想要的是与泛型系统集成的隐式转换,这距离我们现在拥有的还有很长的路要走。

编辑:澄清一下,在您的情况下,将Producer<A>视为Producer<Object>是安全的,但这是泛型系统无法识别的知识。


1

你在 Types 类中实现的并不是一个强制转换,而只是一种方便的方式来隐藏编译器警告(我猜也是愚弄同事的一种方便方式)。因此,与其“强制转换”你的生产者,我建议改变 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);

注意:它可能无法帮助您解决确切的问题,但也许可以指引您朝着正确的方向前进。

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

你对“更好”的理解真是奇特。希望我永远都不用碰你的代码。 - Tom Hawtin - tackline

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接