The differences between the three largely have to do with the checks that you get at compile time. Given that generics were meant to guard the user from an unsafe cast at runtime, the critical and major differences between them are rooted there.
AGenericClass<String>
declares an instance of a generic class specifically of type String
. Any operation done with this generic that requires a generic parameter will have that parameter bound to String
, and will enforce type checking and type safety at compile time.
That is to say, if you have AGenericClass<String> a = new AGenericClass<>();
and you try to call a.setSubject(3)
, Java will not allow the application to compile since the types don't match up.
AGenericClass<?>
declares an instance of a generic class with unknown types. Formally, ?
is a wildcard in which it could be any type, which is fine if you want to retrieve elements from it, but not fine if you want to add elements from it.
The reason? AGenericClass<?>
is actually AGenericClass<? extends Object>
, and that's important because of the in and out principle. Generally (although this isn't a strict guarantee), anything generic with extends
implies a read-only operation.
As I said before, it's fine to read from, since you're guaranteed a class that is at most an Object
, but you can't write to it since you don't know, nor can you guarantee, what kind of object you're adding to it from any given call.
AGenericClass
without the type declarations is a raw type. You can read more about them here, but know that they exist for legacy compatibility reasons. If your code makes use of raw types, you lose the compile time checks and your code may wind up throwing a ClassCastException
during runtime, which is far more difficult to debug, diagnose, and resolve.