So I'm trying to get a reasonably complicated system working. Here's the basics of what I'm attempting.
Rules:
abstract class Rule
{
// stuff
}
class ExampleRule extends Rule
{
// stuff
}
Handlers:
abstract class RuleHandler<T extends Rule>
{
Class<T> clazz;
RuleHandler(Class<T> forClass)
{
this.clazz = forClass;
}
abstract void doStuff(T rule);
}
class ExampleRuleHandler extends RuleHandler<ExampleRule>
{
ExampleRuleHandler()
{
super(ExampleRule.class);
}
void doStuff(ExampleRule rule)
{
// stuff
}
}
And tying them together:
class HandlerDispatcher
{
Map<Class<? extends Rule>, RuleHandler<? extends Rule>> handlers;
void register(RuleHandler<? extends Rule> handler)
{
handlers.put(handler.clazz, handler);
}
void doStuff(List<Rule> rules)
{
for(Rule rule : rules)
{
RuleHandler<? extends Rule> handler = handlers.get(rule.getClass());
handler.doStuff(rule);
}
}
}
class Test
{
void main()
{
HandlerDispatcher hd = new HandlerDispatcher();
hd.register(new ExampleRuleHandler());
}
}
So far I've attempted various combinations of different parameters (wildcarded, restricted, etc.) and have yet to get this compiling without type-related errors. Any insights, solutions or alternative approaches are welcome.
You're trying to use generics in a runtime manner. If you don't know at compile time the types you need to handle, (be it a real type or a type parameter itself), you can't use Generics, plain and simple. It's a compile time only (or mostly) construct.
Here, you're trying to handle things both generically and dynamically. That is generally impossible. Use raw types and live with the type unsafety.
Myself, I'd say you're just asking for trouble with this:
abstract class RuleHandler<T extends Rule>
{
abstract void doStuff(T rule);
}
Just make it this:
abstract class RuleHandler
{
abstract void doStuff(Rule rule);
}
And then only register that rule handler with types it can handle.
Edit
Based on your comment it seems like your goal is to enforce your design on users of your interface. I stand by the argument that you should separate the concept of filtering and handling.
That said, another option is to leave the class token out of the handler itself and generify your register method.
interface RuleHandler<T extends Rule> {
void doStuff(T rule);
}
//...
public <T> void register(Class<T> type, RuleHandler<? super T> handler) {
map.put(type, handler);
}
public void process() {
for ( Rule r : rules ) {
for(Map.Entry<Class<?>, RuleHandler<?>> entry : map.entrySet() ) {
if ( entry.getKey().instanceOf(r) ) {
@SuppressWarnings("unchecked")
((RuleHandler)entry.getValue()).doStuff(r);
}
}
}
}
Here, we suppress a warning when you use the raw type RuleHandler. We know that it is safe through inspection only, by looking at accesses to map
and seeing that the class always matches the RuleHandler
type parameter. However, this will obviously only be safe if there were no type safety warnings for the client when they invoked register()
(i.e. they parameterized the call to register()
).
(The inner for-loop was added over get
such that a handler would be found for subclasses of a given Rule subclasses)
You cannot avoid unchecked casts here.
Each entry in your handlers
map relates a Class<T>
to a RuleHandler<T>
, for some class T
that is different for each entry. The methods of Map
do not express this restriction.
What you can do is create a subclass of Map
that enforces type consistency per entry and perform all the unchecked casts in your new map class.
For an example, look Guava's ClassToInstanceMap.
Observing that
- handlers is private and final
- handler.clazz is final
RuleHandler<T extends Rule>
impliesRuleHandler<?> === RuleHandler<? extends Rule>
the following code is correct (and compiles)
abstract class RuleHandler<T extends Rule>
{
final Class<T> clazz;
// as before
}
class HandlerDispatcher
{
private final Map<Class<?>, RuleHandler<?>> handlers;
void register(RuleHandler<?> handler)
{
handlers.put(handler.clazz, handler);
}
void doStuff(List<Rule> rules)
{
for(Rule rule : rules)
{
@SuppressWarnings("unchecked")
RuleHandler<Rule> handler = (RuleHandler<Rule>) handlers.get(rule.getClass());
handler.doStuff(rule);
}
}
}
class Test
{
void main()
{
HandlerDispatcher hd = new HandlerDispatcher();
hd.register(new ExampleRuleHandler());
RuleHandler<?> handler = new ExampleRuleHandler();
hd.register(handler);
}
}
The problem is this line.
RuleHandler<? extends Rule> handler = handlers.get(rule.getClass());
The compiler doesn't know that <? extends Rule> is the right class because you looked up the correct handler for the Rule's class. You can replace with
RuleHandler handler = handlers.get(rule.getClass());
This will produce a warning because the compiler doesn't know that at runtime, you will pick the right type. If this bothers you, you can add to your class.
@SuppressWarnings("unchecked")
来源:https://stackoverflow.com/questions/4390554/how-do-i-get-this-system-of-nested-generic-parameters-working