庞大的Java类层次结构中的繁琐泛型声明

时间:2015-12-08 07:43:07

标签: java generics

我的项目中基本上有两种类型的实体,只能通过在类泛型声明中指定父目录类型来区分。目录iteself用泛型声明,因为它们可以链接到相同类型的特定旧目录。

abstract class AbstractCatalog<T extends AbstractCatalog<T>> {

    public abstract T getOld();
}

class Catalog1 extends AbstractCatalog<Catalog1> {

    @Override
    public Catalog1 getOld() { ... }
}

class Catalog2 extends AbstractCatalog<Catalog2> {

    @Override
    public Catalog2 getOld() { ... }
}

到目前为止一直很好,但问题是,如果我添加一些必须包含指向某种类型目录的链接的实体,它会变得非常麻烦。

例如,

abstract class AbstractCatalogHistory<C extends AbstractCatalog<C>, E extends AbstractHistoryEntry<C, E>> {

    public abstract Set<E> getEntries();
}

abstract class AbstractHistoryEntry<C extends AbstractCatalog<C>, E AbstractHistoryEntry<C, E>> {

    public abstract E getPrior();
}

class Cat1HistoryEntry extends AbstractHistoryEntry<Catalog1, Cat1HistoryEntry> {

    @Override
    public Cat1HistoryEntry getPrior() { ... }
}

class Cat2HistoryEntry extends AbstractHistoryEntry<Catalog2, Cat2HistoryEntry> {

    @Override
    public Cat2HistoryEntry getPrior() { ... }
}

class Catalog1History extends AbstractCatalogHistory<Catalog1, Cat1HistoryEntry> {

    @Override
    public Set<Cat1HistoryEntry> getEntries() { ... }
}

class Catalog2History extends AbstractCatalogHistory<Catalog2, Cat2HistoryEntry> {

    @Override
    public Set<Cat2HistoryEntry> getEntries() { ... }
}

因此,在查看这样的层次结构时,要了解正在发生的事情变得更加困难。这个例子并不完整,我有几十种类型应该嵌套在我上面提供的那些中。

我这样做的目的是利用可在编译时验证的类型安全代码。但与此同时,这样的代码变得非常混乱,因为我必须在向层次结构中添加新类型时指定更长的泛型链。

有没有办法处理此类仿制药爆炸?

1 个答案:

答案 0 :(得分:2)

您的示例并未完全清楚为什么您需要为Catalog1Catalog2设置单独的类,但我们假设已设置此类。

然而,即使如此,我也没有理由为什么其他引用这些目录的内容都需要这种重复。如果您只是想确保它与正确的目录类型相关联,那么这是您真正需要的唯一通用参数:

class CatalogHistory<C extends AbstractCatalog<C>> {
    public Set<HistoryEntry<C>> getEntries();
}

class HistoryEntry<C extends AbstractCatalog<C>> {
    public HistoryEntry<C> getPrior();
}

但是如果你实际上做了不同的事情,例如Cat1HistoryEntryCat2HistoryEntry所以您需要单独的课程?在这种情况下,您显然无法使用抽象基类和两个具体实现,但我认为不需要引入泛型类型,然后按照您的方式将它们分解为具体类型:

abstract class AbstractHistoryEntry<C extends AbstractCatalog<C>> {

    public abstract AbstractHistoryEntry<C> getPrior();
}

class Cat1HistoryEntry extends AbstractHistoryEntry<Catalog1> {

    @Override
    public Cat1HistoryEntry getPrior() { ... }
}

class Cat2HistoryEntry extends AbstractHistoryEntry<Catalog2> {

    @Override
    public Cat2HistoryEntry getPrior() { ... }
}

这里有一些事情发生。首先,考虑AbstractHistoryEntry。如果您有其中一个,那么您正在开发一个通用级别,而不应该关心getPrior返回这个或那个具体的子类型 - 您需要知道的是它返回引用它的另一个AbstractHistoryEntry对象目录。

如果您有一个具体的Cat1HistoryEntry引用,那么由于Java中返回类型的协方差,您仍然可以获得从Cat1HistoryEntry获得另一个getPrior的完全类型安全性。 / p>

现在它变得稍微复杂了 - 让我们试着用AbstractCatalogHistory拉出相同的技巧:

abstract class AbstractCatalogHistory<C extends AbstractCatalog<C>> {

    public abstract Set<? extends AbstractHistoryEntry<C>> getEntries();
}

class Catalog1History extends AbstractCatalogHistory<Catalog1> {

    @Override
    public Set<Cat1HistoryEntry> getEntries() { ... }
}

class Catalog2History extends AbstractCatalogHistory<Catalog2> {

    @Override
    public Set<Cat2HistoryEntry> getEntries() { ... }
}

如您所见,两个具体子类仍然返回一组具体类型Cat1HistoryEntryCat2HistoryEntry。抽象基类型现在需要为这些集表达一个公共超类型,以便您可以以通用方式处理结果。这是通过引入协方差来完成的。

<强> 塞特斯

塞特斯有点复杂化了这个问题。基本上,如果您有一个像List<T>AbstractCatalogHistory<C>这样的通用容器/集合,并且您希望同时允许添加和检索项目,那么如果您想要类型,则不能再在项目类型中出现差异安全

例如,如果您在AbstractCatalogHistory<C>中设置了一个允许您将任何AbstractHistoryEntry<C>项添加到历史记录中的setter,那么您就会遇到问题,因为如果您的AbstractCatalogHistory<C>实际上是Catalog1History那么你只需要Cat1HistoryEntry个项目!

这与通用列表存在同样的问题:List<Cat>不是List<Mammal>,因为您可以将大象添加到List<Mammal>,但您无法添加大象到List<Cat>

如果您坚持Catalog1的历史记录必须只包含Cat1HistoryEntry个项目,那么解决方案就是只将{b}添加到Catalog1History,而不添加AbstractCatalogHistory<C> }。通过这种方式,泛型类只能用于读取历史记录,而不能用于编写历史记录。

然而,回到我的回答的开头:如果你实际上不需要双具体类,解决方案仍然非常简单。不幸的是,你仍然没有解释为什么或如果你需要那些。如果你真正想要的是Catalog1 / Catalog2区别,那么你实际上并不需要一个不同的实现,例如Cat1HistoryEntryCat2HistoryEntry,则以下内容就足够了:

class CatalogHistory<C extends AbstractCatalog<C>> {
    public Set<HistoryEntry<C>> getEntries();
    public void addEntry(HistoryEntry<C> entry);
}

class HistoryEntry<C extends AbstractCatalog<C>> {
    public HistoryEntry<C> getPrior();
    public void setPrior(HistoryEntry<C> prior);
}