Java类层次结构庞大,泛型声明繁琐。

3

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

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个回答

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中返回类型的协变性获得从getPrior获取另一个Cat1HistoryEntry的完全类型安全。
现在情况稍微复杂了一些——让我们尝试用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>项添加到历史记录中,则会出现问题,因为如果您的AbstractCatalogHistory<C>实际上是Catalog1History,那么您只想在其中放置Cat1HistoryEntry项!
这与通用列表的问题相同: List<Cat>不是List<Mammal>,因为您可以将大象添加到List<Mammal>中,但您不应该能够将大象添加到List<Cat>中。
如果您坚持认为Catalog1的历史记录必须仅由Cat1HistoryEntry项组成,则解决方案是仅向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);
}

Medo42,谢谢!我唯一无法弄清楚的是如何以类型安全的方式实现setter。 - John Doe

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