通用的容器工厂

4

我有一个通用的抽象类Factory<T>,它有一个方法createBoxedInstance(),该方法返回通过createInstance()实现创建的T实例,这些实例被包装在通用容器Box<T>中。

abstract class Factory<T> {
    abstract T createInstance();

    public final Box<T> createBoxedInstance() {
        return new Box<T>(createInstance());
    }

    public final class Box<T> {
        public final T content;

        public Box(T content) {
            this.content = content;
        }
    }
}

有时我需要一个类型为 Box<S> 的容器,其中 ST 的祖先。是否可能使 createBoxedInstance() 本身成为泛型函数,以便它将返回由调用者选择的 S 的实例的 Box<S>?遗憾的是,如下定义函数是不起作用的,因为类型参数不能使用 super 关键字进行声明,只能进行使用。
public final <S super T> Box<S> createBoxedInstance() {
    return new Box<S>(createInstance());
}

我所能看到的唯一选择是使所有需要 Box<S> 实例的位置接受 Box<? extends S>,这样容器的内容成员就可以分配给 S
有没有什么方法可以避免将类型为 T 的实例重新打包到类型为 Box<S> 的容器中?(我知道我可以将 Box<T> 强制转换为 Box<S>,但我会感到非常、非常内疚。)

你尝试过使用抽象类Factory<T,S super T>,然后仅使用T createInstance()来创建实例,而在其他地方则使用S吗?或者尝试过抽象类Factory<T extends S,S>吗? - Luis
如果你想要一个 Box<S>,为什么不使用一个 Factory<S> 呢?问题在于,即使 S 扩展了 T,你也无法知道 Factory<T> 是否创建了一个装箱的 S,它可能是一个装箱的 U(也扩展了 T)。 - Peter Lawrey
@Luis,问题在于我必须事先决定要使用哪种类型来代替S。我的想法是,我可以创建一个T的工厂,然后使用它来创建具有不同类型参数的装箱T。 - Feuermurmel
@Peter Lawrey 工厂创建一个 U,其中 U 扩展 T 是可以的。但在我的例子中,我希望 T 扩展 S,而不是相反。如果我想让 S 在 createBoxedInstance 中扩展 T,我只需在声明前面写上 <S extends T> 即可。 - Feuermurmel
我认为你遇到了Java中常见的问题——没有清洗退出。例如,如果你有一个List<String> a并且想要将其赋值给List<CharSequence> b,你是不行的,因为它们是根本不同的类型,尽管String实现了CharSequence接口。如果这样做,并且你尝试向b中添加一个不是String的CharSequence,那么之后从a中获取它的人就会发生类型违规。 - Luis
@Luis 谢谢你的解释。我知道 List<T>List<S> 在类型系统上没有关联,即使 ST 是相关的。现在我已经接受并拥抱了这个事实,即将一个类型声明为 List<? extends X> 是有可能的,也正是基于这个原因。 - Feuermurmel
3个回答

2
尝试重写你的其他代码,不使用 `Box`,而是使用 `Box`,这样它也将接受 `Box`,从而明确指出 `Box` 可能包含 `S` 的子类。如果你将 Box 设为静态,下面的代码也应该可以工作:
public static <S, T extends S> Box<S> createBoxedInstance(Factory<T> factory) {
  return new Box<S>(factory.createInstance());
}

然而,对于S,它可能无法进行类型推断,这时您需要:

public static <S, T extends S> Box<S> createBoxedInstance(Factory<T> factory, S dummy) {
  return new Box<S>(factory.createInstance());
}

另一种非常明确的变体是:

public static <S, T extends S> Box<? extends S> createBoxedInstance(Test<T> factory, S dummy) {
  return factory.createBoxedInstance(); // Actually a Box<T>
}

这些都是hack,但是你无法从中获得干净的语义。使用Box<? extends S>来承认盒子可能是一个子类型,这样更加明智。 - Has QUIT--Anony-Mousse
尽管我接受这个答案是因为它的建议不要实际上想要一个将Box<T>内部转换为Box<S>的函数,但这个答案包含了最多回答我原始问题的例子。Box<? extends T>才是正确的方法。如果你真的想要一个解决方案来回答我的原始问题,并在内部使用未经检查的强制转换,请查看Ben Schulz的答案 - Feuermurmel

0

你可以在一定程度上实现你想要的:

public final <S extends Box<? super T>> S createBoxedInstance() {
    // sadly we can't capture the wildcard in the bound of S
    // this means we have to cast, but at least it's local..
    // *should* this cast ever break due to reification, we
    // can fix it here and be good to go
    @SuppressWarnings("unchecked")
    S box = (S) new Box<T>(createInstance());
    return box;
}

Factory<Integer> factory = getFactory();
Box<Number> box1 = factory.createBoxedInstance();
Box<Object> box2 = factory.createBoxedInstance();

我认为你所提出的Box<? extends S>替代方案是正确的。然而,如果这样会导致客户端代码被大量通配符所污染,那么上述做法或许更好。


我已经在被接受的答案的评论中链接了你的回答,但我同意你(和其他人)的建议,使用Box<? extends S> - Feuermurmel

0
你不能这样做(或者必须使用奇怪的转换)的原因如下: 想象一下,如果Box内容不仅是final还有setter。此外,假设类型S是R和T的超类型。以下代码将在没有任何强制转换的情况下有效。
Box<T> boxT = new Box<T>(objectT);
Box<S> boxS = boxT;
S objectR = new R();
boxS.set(objectR);

这段代码在没有警告的情况下是有效的,但是setter会在运行时失败并抛出意外异常(意外在于不明显地可以抛出转换异常),因为您不能将类型为R的内容分配给类型为T的内容。

您可以在http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#Topic3阅读更多信息(但最好准备阿司匹林,因为您可能会头疼)

正如许多人所说,也许在其他代码中使用box是最好的选择。


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