0

I have the following static method:

class Foo {
    public static <T> List<T> populate(ResultSet rs, Class<? extends T> klass) {
        // ...
    }

    // ...
}

And I have

class MyConcreteClass extends MyAbstractBaseClass {
    // ...
}

When I try to call the abstract method like this:

List<MyAbstractBaseClass> result = Foo.populate(rs, MyConcreteClass.class)

… then I get the following compilation error:

Compilation failure
List<MyConcreteClass> cannot be converted to List<MyAbstractBaseClass>

However, if I call it like this:

List<MyAbstractBaseClass> result = Foo.<MyAbstractBaseClass>populate(rs, MyConcreteClass.class)

… then the compiler seems satisfied.

My question:

  1. Why does Java require an explicit type parameter in the above example?
  2. Given that my result must be a List<MyAbstractBaseClass>, is there any way I can specify the signature of my static method differently, such that I don’t have to provide the type parameter, when I call it?

Edit: Specified that the result type is a given, and cannot be changed.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
KaptajnKold
  • 10,638
  • 10
  • 41
  • 56
  • 2
    See https://stackoverflow.com/questions/2745265/is-listdog-a-subclass-of-listanimal-why-are-java-generics-not-implicitly-po – ewramner Aug 02 '22 at 14:10
  • Note that `Class` doesn't play very nicely with generics, because you can't get class literals for parameterized types. Assuming you're using `klass` to create instances of the type, it would be better to provide something like a `Supplier extends T>`. – Andy Turner Aug 02 '22 at 14:16
  • An other option is to change the signature to `public static List super T> populate(ResultSet rs, Class extends T> klass)`. This may or may not require you to change some declaration inside `populate`, but otherwise it should work as you wish on the call site. – Johannes Kuhn Aug 02 '22 at 14:37

3 Answers3

3

Foo.populate(rs, MyConcreteClass.class) returns a List<MyConcreteClass>.

However, a List<MyConcreteClass> is not assignable to a List<MyAbstractBaseClass> because genetics are invariant in Java (in contrast to arrays).

In your case, the problem is type inference. Java infers the type using parameters if possible and falls back to the left side of the assignment if no parameters can be used for inference.

Explicitly specifying the type can overrule this behaviour.

You can also get around that by explicitly declaring the Class type:

Class<? extends MyAbstractBaseClass> cl = MyConcreteClass.class; //perfectly legal because of the lower bound
List<MyAbstractBaseClass> result = Foo.populate(rs, cl);

Another possibility would be to use a safe cast like this:

List<MyAbstractBaseClass> result = Foo.populate(rs, (Class<? extends MyAbstractBaseClass>) MyConcreteClass.class);

This cast is always correct because Class<? extends MyConcreteClass> is by definition a Class<? extends MyAbstractBaseClass>.

dan1st
  • 12,568
  • 8
  • 34
  • 67
2

Both these are legal:

List<? extends MyAbstractBaseClass> result = Foo.populate(rs, MyConcreteClass.class);
List<MyConcreteClass> result = Foo.populate(rs, MyConcreteClass.class);

However

// *** ERROR
List<MyAbstractBaseClass> result = Foo.populate(rs, MyConcreteClass.class);

would give assign a List to that list. If the original list would be referenced elsewhere, thinking only MyConcreteClass objects were in it, but result would allow other classes to be added.

2.

Of course there now is

var result = ...

which is evidently confusing.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
2

Simplifying your example (the ResultSet isn't relevant):

public static <T> List<T> populate(Class<? extends T> klass) {
    return null;
}

The following compiles fine on Ideone:

List<Object> objList = populate(Object.class);
List<Object> intList = populate(Integer.class);

Ideone is using Java 12; I notice that you've tagged the question Java 8.

The type inference in Java 8 has been improved in more recent versions of Java.

If you're stuck on Java 8, you've just got to provide the type witness.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243