The general conclusion is that using java.lang.Class
in this way is a code smell; you should be using a factory instead. Class
is a very bad class (heh) to attempt to understand generics with. So let's explain all this by using a different example. It is important to keep in mind that the compiler does not know what a List is. It's just.. a class. With a type variable. So is java.lang.Class
. Hence, the compiler treats them the same way. It is not going to reason differently about the generics based on knowledge that Class
is a bit bizarre ^1.
For our example, we'll use java.util.List
, and for the things this list stores, we need a dimensional axis of types, so we'll use java.lang.Object
, which is the supertype of java.lang.Number
, which is the supertype of java.lang.Integer
and java.lang.Double
. For the purposes of this example we'll state that no other numbers exist in the core libraries at this point. Note that Number
is not final
.
Let's start simple:
List<? extends Number>
How do you read that out loud?
I often hear those newer to java, or even experts, read that out loud as "A List of things that extend Number" and that is wrong. That is not what that says. After all, java is covariant (at least, outside of generics it is), so when I write Number n;
in java, that means 'n' is a reference that references Number or anything that is an instance of a subtype of Number. In fact, Number
is abstract. If the explanation about the difference between the 2 kinds of 'list of numbers' is:
// Note, this is wrong! Used as an illustration!
List<? extends Number> - a List of instances of Number or subtypes of Number.
List<Number> - a List of instances of specifically Number, not subtypes
Then that'd be ridiculous. The second list cannot possibly have any members; class Number
is abstract! new Number()
is a compiler error. By definition, every instance of Number
is actually an instance of a subtype of it.
So, that's the wrong interpretation. What's right? Well, the question mark gives it away. The correct interpretation is I do not know. That's how you read that. You read that as: "A List of... I do not know! - all I know is, whatever this list's elements are, they are constrained to be instances of some unknown type (meaning: They are all an instance of that type or a subtype of that type), and I know that, whatever the type is that forms this upper bound, it's either Number
, or some subtype of it".
That's a lot of words. An easier way to think about it is this:
It is any of these 3 things; any of these are fine and are all of type List<? extends Number>
:
new ArrayList<Number>();
new ArrayList<Integer>();
new ArrayList<Double>();
So, which one is it? You do not know! - and the compiler knows you don't know. The compiler will force you into interacting with this expression only in ways that are guaranteed to be make sense regardless of which of those 3 it is.
Take, for example, add
. What can you add to a list where you don't know which of the above 3 constructors made that list?
How about Number n = 5 /* an integer */; list.add(n);
?
No, the compiler will not allow this. After all, that is invalid if the list is a List<Double>
. After all, that would shove an integer into a list that says it cannot contain integers. A violation.
In fact, nothing is the answer. There's nothing you can add to a List<? extends Number>
. Except the null
literal which happens to be every type at once, but list.add(null);
is generally quite the useless statement.
This is why generics are invariant. Because the basic rules of logic dictate it. It's a law of the universe, essentially.
j.l.Class
is a bit of an oddball. If we look at the methods available to List
, we see loads of places where the type parameter is used as type of a method parameter. add(T elem)
, obviously. There's also addAll(Collection<? extends T> elems)
, and also .sort(Comparator<? super T> comparator)
. And many more.
But j.l.Class
isn't like that. None of the methods available on j.l.Class
take the type param as parameter. A few employ the type param as return type, though.
This is where 'PECS' comes in: Producing Extends, Consuming Super.
To explain that, we go back to lists's addAll
. Why is its argument Collection<? extends T>
and not just Collection<T>
? Because, well, think about it. a Collection<? extends Number>
can be Collection<Number>
, Collection<Integer>
, or Collection<Double>
. However, copying all elements of any of those 3 into a List<Number>
is totally fine. Nothing is broken. Hence why it is written that way: If it wasn't, you couldn't call list.addAll(someListOfIntegers)
if list
is List<Number>
and that's incorrect/inconvenient; That call should work. And indeed it does. The key clue here is that the addAll
method only requires that the incoming parameter 'produces' (returns) the typeparameter (for example, list.get(0)
would 'produce'). It does not at any time have to 'consume' it (for example, list.add(elem)
'consumes' a type parameter). Hence PECS: Only producing? Then use extends
.
To explain CS, we look at sort
. Sort only consumes! We ask that comparator
only to consume our type param. The only method we are going to call on it is int whichOneIsHigher(T elem1, T elem2)
- we are not asking this comparator to produce values of type T, it'll only tell us 'higher / lower / the same'. Hence, if we have a comparator that can compare any 2 objects, we can use it to sort a list of numbers. However, if we have a comparator that can only compare Integer instances, and we have a List<Number>
, we can't sort our list. After all, what if our list contains a few Double instances? Our comparator has no idea how to handle them, so, that's a type error. Hence, a Comparator<Number>
can work, a Comparator<Object>
can also work. We only ask the object we call to 'consume', so, CS: Only consume? Use super
.
java.lang.Class
is an oddball in that its entire API consists solely of production. None of its methods consume. Hence, it is hard to explain in obvious terms what the difference is between the type Class<? extends Something>
and Class<Something>
. Nevertheless, the compiler does not know that and will not treat these types as equal.
Handler<? extends Something> handler = new Handler<>();
handler = new Handler<Something>(); // will not compile
Read these things out loud correctly and it should be obvious. You've claimed that your field handler
has the type of: "A Handler instance that handles.. I do not know. All I know is, it is a handler of either Something, or some subtype of Something.".
Given that you do not know, handler = new Handler<Something>()
is not valid. After all, what if other code thinks it's a handler of Shoes? It might ask the handler to produce a Shoe. And given that you've made a handler of Something
, it could produce a Shawl
and the code that thought this handler could only ever produce shoes is now broken. That's a type violation and the compiler therefore will not let you.
How do you fix it? Depends; you haven't provided enough information to know what's right. Possibly it's this:
public class Handler<S extends Something> {
void registerHandler(Class<? extends S> someClass) {
// do something
}
}
--And--
Handler<Something> handler = new Handler<>();
void doStuff() {
handler.registerHandler(Shoe.class);
}
Class
considered harmfulIt's extremely rare you want a Class<T>
as a field at all. Almost always the intent is to call newInstance
. This doesn't work, because it is not possible to capture the notion of 'has a no-args, public constructor' in the type system. I am free to make a class that doesn't have one of those constructors and you won't know the code is broken until you run it. The point of the type system is to tell you your code is broken before you run it. i.e. as you write it.
The solution is factories. This is the general solution to the concept of 'A type itself has various properties I would ordinarily capture in an interface'. One common property is 'You can make instances of this type by passing these parameters to it'. It's so common, these things that capture 'things the type itself can do' are called 'factories'. But the principle is more abstract than that.
This is an example of broken design that's somewhat common:
class Animal {
abstract String noise();
}
class Dog {
@Override String noise() {
return "BARK!";
}
}
This is broken because the notion 'dogs bark' is inherent to the type and not to any particular dog. In other words, this method has no business being an instance method. Why can I only call 'noise' on an actual dog? If I ask you 'what noise does a dog make?', you tell me 'a bark'. You don't tell me: "Well, I do not know. Which specific dg are you talking about? Fido, or rover?".
Whereas if I ask you: "What is the name of a dog?", you cannot answer this question. That is only answerable if I bring you or otherwise point at a specific dog. What is the name of that dog?", then you can answer.
The better design is this:
abstract class PetAnimal {
private final String name;
public PetAnimal(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
abstract class AnimalFactory<A extends Animal> {
abstract A create(String name);
abstract String noise();
}
class Dog extends PetAnimal {
public Dog(String name) {
super(name);
}
}
class DogFactory extends AnimalFactory<Dog> {
public static final INSTANCE = new DogFactory();
private DogFactory() { /* prevent instantiation */ }
@Override public Dog create(String name) {
return new Dog(name);
}
@Override public String noise() {
return "BARK!";
}
}
We now have the type safety we wanted: There is no way this code can fail without the compiler being aware of it (i.e. knowing at write time) due to a constructor with the wrong parameters. And I can ask the question "What noise do dogs make?" without needing a dog. (I would need the factory though, but that's trivial; DogFactory.INSTANCE
).
[1] To make 'bizarre' a bit more specific: That Class
essentially only ever 'produces' and thus, as per PECS, there is no meaningful difference to you and me with full knowledge of this between Class<Something>
and Class<? extends Something>
.