Home > Back-end >  Why does Java require an explicit type parameter here?
Why does Java require an explicit type parameter here?

Time:08-02

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.

CodePudding user response:

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>.

CodePudding user response:

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.

CodePudding user response:

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.

  • Related