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, proces开发者_开发知识库sor);
}
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);
}
}
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.
精彩评论