java泛型声明需要派生类

发布于 2024-08-17 10:07:38 字数 4729 浏览 9 评论 0 原文

我遇到了一个棘手的问题,我似乎无法用 java 泛型解决它。这有点复杂,但我想不出一个更简单的场景来说明问题......这里是:

我有一个需要 Context 的 Processor 类。上下文有不同类型;大多数处理器只需要任何抽象上下文,但其他处理器则需要特定的子类。像这样:

abstract class AbstractProcessor<C extends Context> {
    public abstract void process(C context);
}

class BasicProcessor extends AbstractProcessor<Context> {
    @Override
    public void process(Context context) {
        // ... //
    }
}

class SpecificProcessor extends AbstractProcessor<SpecificContext> {
    @Override
    public void process(SpecificContext context) {
        // ... //
    }
}

好吧,很酷:处理器可以声明他们需要的 Context 类型,并且他们可以假设正确的类型将被传递到 process() 中而无需强制转换。

现在,我有一个 Dispatcher 类,它拥有字符串到处理器的映射:

class Dispatcher<C extends Context> {
    Map<String, AbstractProcessor<? super C>> processorMap = new HashMap<String, AbstractProcessor<? super C>>();

    public void registerProcessor(String name, AbstractProcessor<? super C> processor) {
        processorMap.put(name, processor);
    }

    public void dispatch(String name, C context) {
        processorMap.get(name).process(context);
    }
}

好的,到目前为止一切顺利!我可以为特定类型的上下文创建一个调度程序,然后注册一批可能需要该上下文类型的任何抽象的处理器。

现在,问题是:我希望抽象 Context 类型拥有 Dispatcher,并且派生的 Context 类型应该能够注册其他处理器。这是我能找到的最接近工作解决方案的方法,但它并不能完全工作:

class Context<C extends Context> {
    private final Dispatcher<C> dispatcher = new Dispatcher<C>();

    public Context() {
        // every context supports the BasicProcessor
        registerProcessor("basic", new BasicProcessor());
    }

    protected void registerProcessor(String name, AbstractProcessor<? super C> processor) {
        dispatcher.registerProcessor(name, processor);
    }

    public void runProcessor(String name) {
        dispatcher.dispatch(name, this); // ERROR: can't cast Context<C> to C
    }
}

// this is totally weird, but it was the only way I could find to provide the
// SpecificContext type to the base class for use in the generic type
class SpecificContext extends Context<SpecificContext> {
    public SpecificContext() {
        // the SpecificContext supports the SpecificProcessor
        registerProcessor("specific", new SpecificProcessor());
    }
}

问题是我需要在 Context 基类中声明一个通用调度程序,但我希望类型变量引用特定的 派生 类型。如果不在每个 Context 子类中复制一些代码(特别是 Dispatcher 和 registerProcessor 方法的构造),我看不到一种方法可以做到这一点。这就是我认为我真正想要的:

Dispatcher<MyRealClass> dispatcher = new Dispatcher<MyRealClass>();

有没有一种方法可以使用声明类的子类的类型来声明对象的泛型类型?

是的,我可以通过一些低风险的选角来解决这个问题,所以这主要是一个学术问题......但我很想找到一个从上到下有效的解决方案!你能帮忙吗?您将如何处理这种架构?


更新

这是完整的源代码,已更新以纳入 Andrzej Doyle 使用 > 的建议;它仍然不起作用,因为 Context; != C:

class Context<C extends Context<C>> {
    private final Dispatcher<C> dispatcher = new Dispatcher<C>();

    public Context() {
        // every context supports the BasicProcessor
        registerProcessor("basic", new BasicProcessor());
    }

    protected void registerProcessor(String name, AbstractProcessor<? super C> processor) {
        dispatcher.registerProcessor(name, processor);
    }

    public void runProcessor(String name) {
        dispatcher.dispatch(name, this); // ERROR: can't cast Context<C> to C
    }
}

// this is totally weird, but it was the only way I could find to provide the
// SpecificContext type to the base class for use in the generic type
class SpecificContext extends Context<SpecificContext> {
    public SpecificContext() {
        // the SpecificContext supports the SpecificProcessor
        registerProcessor("specific", new SpecificProcessor());
    }
}

abstract class AbstractProcessor<C extends Context<C>> {
    public abstract void process(C context);
}

class BasicProcessor extends AbstractProcessor {
    @Override
    public void process(Context context) {
        // ... //
    }
}

class SpecificProcessor extends AbstractProcessor<SpecificContext> {
    @Override
    public void process(SpecificContext context) {
        // ... //
    }
}

class Dispatcher<C extends Context<C>> {
    Map<String, AbstractProcessor<? super C>> processorMap = new HashMap<String, AbstractProcessor<? super C>>();

    public void registerProcessor(String name, AbstractProcessor<? super C> processor) {
        processorMap.put(name, processor);
    }

    public void dispatch(String name, C context) {
        processorMap.get(name).process(context);
    }
}

I've run into a sticky problem that I can't seem to solve with java generics. This is a bit complicated, but I couldn't think of a simpler scenario to illustrate the problem... Here goes:

I have a Processor class that requires a Context. There are different types of Context; most processors just need any abstract Context, but others require a specific subclass. Like this:

abstract class AbstractProcessor<C extends Context> {
    public abstract void process(C context);
}

class BasicProcessor extends AbstractProcessor<Context> {
    @Override
    public void process(Context context) {
        // ... //
    }
}

class SpecificProcessor extends AbstractProcessor<SpecificContext> {
    @Override
    public void process(SpecificContext context) {
        // ... //
    }
}

Ok, cool: Processors can declare the type of Context they need, and they can assume the right type will be passed into process() without casting.

Now, I have a Dispatcher class that owns a mapping of Strings to Processors:

class Dispatcher<C extends Context> {
    Map<String, AbstractProcessor<? super C>> processorMap = new HashMap<String, AbstractProcessor<? super C>>();

    public void registerProcessor(String name, AbstractProcessor<? super C> processor) {
        processorMap.put(name, processor);
    }

    public void dispatch(String name, C context) {
        processorMap.get(name).process(context);
    }
}

Ok, so far so good! I can create a Dispatcher for a specific type of Context, then register a batch of processors that may expect any abstraction of that Context type.

Now, here's the problem: I want the abstract Context type to own the Dispatcher, and derived Context types should be able to register additional Processors. Here's the closest I could find to a working solution, but it doesn't fully work:

class Context<C extends Context> {
    private final Dispatcher<C> dispatcher = new Dispatcher<C>();

    public Context() {
        // every context supports the BasicProcessor
        registerProcessor("basic", new BasicProcessor());
    }

    protected void registerProcessor(String name, AbstractProcessor<? super C> processor) {
        dispatcher.registerProcessor(name, processor);
    }

    public void runProcessor(String name) {
        dispatcher.dispatch(name, this); // ERROR: can't cast Context<C> to C
    }
}

// this is totally weird, but it was the only way I could find to provide the
// SpecificContext type to the base class for use in the generic type
class SpecificContext extends Context<SpecificContext> {
    public SpecificContext() {
        // the SpecificContext supports the SpecificProcessor
        registerProcessor("specific", new SpecificProcessor());
    }
}

The problem is that I need to declare a generic Dispatcher in the base Context class, but I want the type-variable to refer to the specific derived type for each Context sub-type. I can't see a way to do this without duplicating some code in each Context subclass (specifically, the construction of the Dispatcher and the registerProcessor method). Here's what I think I really want:

Dispatcher<MyRealClass> dispatcher = new Dispatcher<MyRealClass>();

Is there a way to declare the generic type of an object with the type of the SUBCLASS of the declaring class?

Yes, I can address this problem with a little bit of low-risk casting, so this is mostly an academic question... But I'd love to find a solution that just works top-to-bottom! Can you help? How would you approach this architecture?


UPDATE:

Here's the full source, updated to incorporate Andrzej Doyle's suggestion to use <C extends Context<C>>; it still doesn't work, because Context<C> != C:

class Context<C extends Context<C>> {
    private final Dispatcher<C> dispatcher = new Dispatcher<C>();

    public Context() {
        // every context supports the BasicProcessor
        registerProcessor("basic", new BasicProcessor());
    }

    protected void registerProcessor(String name, AbstractProcessor<? super C> processor) {
        dispatcher.registerProcessor(name, processor);
    }

    public void runProcessor(String name) {
        dispatcher.dispatch(name, this); // ERROR: can't cast Context<C> to C
    }
}

// this is totally weird, but it was the only way I could find to provide the
// SpecificContext type to the base class for use in the generic type
class SpecificContext extends Context<SpecificContext> {
    public SpecificContext() {
        // the SpecificContext supports the SpecificProcessor
        registerProcessor("specific", new SpecificProcessor());
    }
}

abstract class AbstractProcessor<C extends Context<C>> {
    public abstract void process(C context);
}

class BasicProcessor extends AbstractProcessor {
    @Override
    public void process(Context context) {
        // ... //
    }
}

class SpecificProcessor extends AbstractProcessor<SpecificContext> {
    @Override
    public void process(SpecificContext context) {
        // ... //
    }
}

class Dispatcher<C extends Context<C>> {
    Map<String, AbstractProcessor<? super C>> processorMap = new HashMap<String, AbstractProcessor<? super C>>();

    public void registerProcessor(String name, AbstractProcessor<? super C> processor) {
        processorMap.put(name, processor);
    }

    public void dispatch(String name, C context) {
        processorMap.get(name).process(context);
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

去了角落 2024-08-24 10:07:38

听起来您的问题是您需要泛型来引用子类的特定确切类型,而不是从父类继承泛型定义。尝试将您的 Context 类定义为

class Context<C extends Context<C>>

注意通用参数的递归使用 - 这有点难以理解,但它 强制子类准确引用其自身。 (说实话,我不太明白这一点,但只要你记住它有效,它就有效。作为参考,Enum 类的定义完全是在同样的方式。)Angelika Langer 的泛型常见问题解答中还有一个部分 涵盖了这一点

通过这种方式,编译器可以获得有关允许的类型的更多信息,并且应该允许您的案例在没有多余转换的情况下进行编译。

更新:经过更多的思考,我的上述评论是正确的,但并不完全是为了钱。如上所述,使用自递归泛型边界,您永远无法真正使用定义它们的实际类。事实上,我以前从未完全注意到这一点,因为幸运或判断,我显然总是在类层次结构的正确位置使用它。

但我花时间尝试编译你的代码 - 并意识到了一些事情。具有这些边界的类永远不能被引用为自身,它只能在特定子类的上下文中被引用。例如,考虑 BasicProcessor 的定义 - ContextAbstractProcessor 的通用边界中似乎未泛化。为了防止出现原始类型,有必要将类定义为:

class BasicProcessor extends AbstractProcessor<Context<Context<Context<...

子类可以避免这种情况,因为它们在定义中包含递归性:

class SpecificContext extends Context<SpecificContext>

我认为这从根本上来说是问题所在 - 编译器无法保证 C 和 Context 是相同的类型,因为它没有所需的特殊大小写逻辑来确定这两个实际上是等效类型(实际上只能是当通配符链接是无限的情况下,因为在任何非无限意义上,后者在扩展时总是比第一个更深一层)。

所以这不是一个很好的结论,但我认为在这种情况下你需要进行强制转换,因为否则编译器无法导出自身的等价性。或者,如果您在类似位置使用 Context 的具体子类,编译器能够解决这个问题,这不会成为问题。

如果您确实找到了一种无需强制转换或无需插入虚拟子类即可实现此功能的方法,请报告回来 - 但我看不到一种方法可以做到这一点,这可以与 Java 泛型可用的语法和语义一起使用。

It sounds like your problem is that you need the generics to refer to the specific exact type of the subclass, rather than inheriting the generic definition from the parents. Try defining your Context class as

class Context<C extends Context<C>>

Note the recursive use of the generic parameter - this is a bit hard to wrap one's head around, but it forces the subclass to refer to exactly itself. (To be honest I don't quite fully get this, but so long as you remember that it works, it works. For reference, the Enum class is defined in exactly the same way.) There's also a section in Angelika Langer's Generics FAQ that covers this.

This way the compiler gets more information about exactly what types are permissable, and should allow your case to compile without the superfluous casting.

UPDATE: Having thought about this a bit more, my above comments were along the right track but were not entirely on the money. With self-recursive generic bounds, as above, you can never really use the actual class you define them on. I'd actually never fully noticed this before, as by luck or judgement I'd apparently always used this in the right point of the class hierarchy.

But I took the time to try and get your code to compile - and realised something. The class with these bounds can never be referred to as itself, it can only ever be referred to in the context of a specific subclass. Consider the definition of BasicProcessor for example - Context appears ungenerified in the generic bounds for AbstractProcessor. To prevent a raw type from appearing, it would be necessary to define the class as:

class BasicProcessor extends AbstractProcessor<Context<Context<Context<...

This is avoided with subclasses because they incorporate the recursiveness in their definition:

class SpecificContext extends Context<SpecificContext>

I think this is fundamentally the problem here - the compiler cannot guarantee that C and Context<C> are the same types because it doesn't have the required special-casing logic to work out that the two are actually an equivalent type (which can only actually be the case when the wilcard chaining is infinite, since in any non-infinite sense the latter is always one level deeper than the first when expanded).

So it's not a great conclusion, but I think in this case your cast is needed because the compiler is unable to derive the equivalence for itself otherwise. Alternatively, if you were using a concrete subclass of Context in a similar position the compiler is able to work it out and this would not be a problem.

If you do happen to find a way to get this working without casting or having to insert a dummy subclass then please report back - but I can't see a way to do that, that would work with the syntax and semantics available to Java's generics.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文