ARTICLE AD BOX
The question mark is best read as 'don't know', and generics are best read as 'restricted to'. This makes clear what they are. Which is: Figments of the compiler's imagination. Compiler-checked documentation. It's just meta instructions to the compiler so that the compiler automatically injects casts and does some additional type checks. Generics are, to the runtime, non-existent (erased) or comments (in the class file purely for the purposes of letting javac know what to do; javac also reads class files, it's not just the JVM itself).
List<? extends Number> thus becomes:
A List that is restricted to only containing.. I do not know. All I know is, whatever the restriction is, it's either Number, or some subtype of Number.
Why your code doesn't compile
Looking at your code:
handlers = new HashMap<CommandName, CommandHandler<? extends Command<? extends Args>>>();Your map now maps commandname objects to a commandhandler, but any commandhandler that is retrieved from this map is a command handler that handles I do not know.
Given that you do not know, there is no value you can possibly obtain that is valid to pass as command to such a handler, except literally the null literal, as that is every type.
Let's put it in terms of lists. Let's go back to that List<? extends Number>. That means: A list restricted to _I do not know, all I know is, Number or some subtype of Number'.
new ArrayList<Integer>() is a valid expression to pass as parameter to a List<? extends Number> argument. After all, it fits inside that restriction. So is new ArrayList<Double>(). Therefore, if you have a method that takes as argument a List<? extends Number>, it could be either of those two types. There is no type that could be added to either of those lists. Nothing is both an Integer and a Double at the same time. Well, except the null literal.
That's why you can't call .add() on a list <? extends Anything>, ever. (And <?> is short for <? extends Object>).
For the same reason, you can't call handle on a CommandHandler<? extends Anything>, ever. Because the compiler can't type check it.
No reification
Generics, as mentioned, are a figment of the compiler's imagination. Any runtime introspection methods as a rule simply cannot obtain these restrictions in runtime form because generics is usually erased. In other words, given:
void foo(List<?> list) { System.out.println("List restriction: " + ?????); } foo(new ArrayList<String>());There is nothing you could write in place of those question marks that would get you 'String'. Because it's been erased.
Therefore then you cannot write some runtime code that does something like 'if the handler can indeed handle this command, then call it, otherwise, throw an exception'. All you can do is tell the compiler to shut its piehole, stop worrying about the fact that it cannot ensure type safety, and just call it already shut up with your warnings and errors, and then hope for the best. If after all that handler couldn't actually handle that command, you'd get a ClassCastException (no core dumps, fortunately), but, that exception will occur on a line that has no casts at all. So, it'll be a confusing error. You can do this by casting to raw types:
List<Integer> list = new ArrayList<>(); List raw = list; List<Double> doubles = (List<Double>) raw; doubles.add(5.0);The above compiles, and even runs. No errors. But, now there's a double in a list of integers. And there's just one list here of course; only one new statement. list, raw, and doubles are all references referring to the exact same list. Which means...
Integer i = list.get(0);Compiles, but when you run that line, you'd get... a ClassCastException, which is weird, as that line contains no cast! That's what happens when you tell the compiler to just get on with it and stop whining, which we had to do here - the previous snippet, while it compiles and runs without errors, certainly does throw warnings at you when you compile it: The compiler is telling you it can't give you type safety. It's telling you the 'weird thing' (ClassCastEx on a line with no casts) can happen.
Applying that to your code
Simply make that a Map<CommandName, CommandHandler>. No further generics on that handler. Yes, you'll get warnings every time you interact with it, but then that's what you wrote. There is no way to get compile-time type safety here. Not because java is too limited to understand that your code is safe - but, because you wrote it on a way that a compiler cannot determine this.
Alternate strategies
The real 'loss' in your code of type safety occurs here:
public void registerHandler( final CommandName name, final CommandHandler<? extends Command<? extends Args>> handler) { handlers.put(name, handler); }This code has absolutely no indication that this stuff needs to line up. I can do:
registerHandler(CommandName.count, new CommandHandler<TranscribeCommand<TranscribeArgs>>() { ... });And I have now registered a handler that can only handle 'transcribe' commands with the 'count' command. Unfortunately, enum values cannot have generics. For a 'register a handler' construction here, you pick: Either [A] enums but no generics, or [B] generics, no enums.
In general I'd advise against the generics. Just cast. CommandHandlers accept any command and any args and are responsible for casting the command to the thing they can handle, throwing an exception if they can't. This is easy in modern java:
public class CountHandler extends CommandHandler { @Override public void handle(Command rawCommand) { if (!(rawCommand instanceof CountCommand command)) throw new IllegalArgumentException("CountHandler can only handle CountCommand, not " + rawCommand); command.doWhateverYouWant(); //command is of type CountCommand. } }But I want the safety!
If you must, you need to 'link' quite a few things. This starts with the register method, which needs to link the CommandName to the subtype of Command that the name would use. You now have double bookkeeping - both names and command types, which is suboptimal. At that point, ditch the generics, decree that command types themselves can't have generics (because then you need higher order generics which java doesn't have), so ditch the generics for 'args', and use the type itself as the 'name':
public <C extends Command> registerHandler(Class<C> type, CommandHandler<C> handler) {}I want the args too!
I don't think that's possible.
