2

I am in the creation of a small application and I stumbled over the following problem.

There is a List<Class<MyCustomBaseClass>> in my application and a function with the signature public <T extends MyCustomBaseClass> void addClass(Class<T> clazz).
The AddClass should put the clazz into the List. But I get the following error there:

The method add(Class<MyCustomBaseClass>) in the type List<Class<MyCustomBaseClass>> is not applicable for the arguments (Class<T>)

Here are my 3 classes as simplified as I could make them:

// Program.java
package me.mischa.stackoverflow;

import java.util.ArrayList;
import java.util.List;

public class Program {

    private List<Class<MyCustomBaseClass>> _listOfClasses;
    private static Program _instance;

    public Program() {
        _listOfClasses = new ArrayList<>();
    }

    public static void main(String[] args) {
        Program program = new Program();
        program.addClass(MyCustomChildClass.class);
    }

    public <T extends MyCustomBaseClass> void addClass(Class<T> clazz) {
        _listOfClasses.add(clazz);
    }

}

.

// MyCustomBaseClass.java
package me.mischa.stackoverflow;

public class MyCustomBaseClass {

}

.

// MyCustomChildClass.java
package me.mischa.stackoverflow;

public class MyCustomChildClass extends MyCustomBaseClass {

}

The error is at the line _listOfClasses.add(clazz);

I do not understand why <T extends MyCustomBaseClass> should not be compatible with <MyCustomBaseClass>

Mischa
  • 1,303
  • 12
  • 24
  • A `List` can point to a `List`. But a `List` does not allow `Cat`s to be inserted, in contrast to a `List`. Both lists behave differently. – Zabuzard Sep 11 '18 at 00:39

2 Answers2

3

Java's generics are invariant. That means that, as a type parameter, Class<MyCustomBaseClass> means exactly that, no Class object representing a subclass of MyCustomBaseClass is allowed.

In your addClass method, you've only given an upper bound on T when defining it -- T could be a subclass of MyCustomBaseClass, e.g. your class MyCustomChildClass. The compiler disallows this call because of the mismatch.

You can widen what's allowed in _listOfClasses by providing a matching upper bound, which will allow the method addClass to compile.

private List<Class<? extends MyCustomBaseClass>> _listOfClasses;

Incidentally, because it doesn't really matter exactly what type T is in addClass, you can remove it and use a wildcard.

public void addClass(Class<? extends MyCustomBaseClass> clazz) {
rgettman
  • 176,041
  • 30
  • 275
  • 357
0

Java's generics are invariant. There's a reason for that. Imagine the following code (NOTE: Number is a supertype of both Integer and Double):

List<Double> doublesOnly = new ArrayList<Double>();
List<Number> numbers = doublesOnly;
numbers.add(new Integer(5));
Double d = doublesOnly.get(0);

In the above, if it had been valid java, you are assigning an Integer to a Double, which is a problem because an Integer isn't a Double. This is why in actual fact, if you attempt to compile the above, it won't work; the second line is marked as invalid java, because a List<Double> cannot be assigned to a List<Number>. There is a solution:

List<Double> doublesOnly = new ArrayList<Double>();
List<? extends Number> numbers = doublesOnly;
numbers.add(new Integer(5));
Double d = doublesOnly.get(0);

This time, line 3 is the error: You cannot add anything to a List<? extends Something>, other than null. There's no way to fix this code and that's good because we're doing something fundamentally bad.

The solution in your specific case is two-fold:

  1. More generally you should avoid the notion of using Class<?> in your APIs. Generally, use factories instead.

  2. If you must, try something like: List<Class<? extends MyCustomBaseClass>>. Yes, 2 extends. You can add a Class<MyCustomChildClass> to this list.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72