Christian
Christian

Reputation: 6440

Why does the Java compiler not let me return a generic type?

I'm a bit at a loss with generics. I have the following code:

public interface SampleValue<T> {
    T getValue();
}

public static class SampleBoolean implements SampleValue<Boolean> {
    @Override
    public Boolean getValue() {
        return Boolean.TRUE;
    }
}

public static final class SampleValueGenerator {
    private SampleValueGenerator() {
        // do not call
    }

    public static <T, R extends SampleValue<T>> R forType(Class<T> clazz) {
        if(Boolean.class.equals(clazz)) {
            return new SampleBoolean();
        }
    }
}

When I try this, IntelliJ (i.e. the compiler) tells me that R and SampleBoolean are incompatible types (for the return line).

When I try the non-generic (raw) return type

public static <T> SampleValue forType(Class<T> clazz) {

I don't get any error;

public static <T, R extends SampleValue<?>> R forType(Class<T> clazz) {

(with the ? wildcard) however fails again. And for

public static <T> SampleValue<T> forType(Class<T> clazz) {

I get Incompatible types, Found: SampleBoolean, Required: SampleValue<T>.

My guess is that it has to do with e.g. List not being an ancestor of List (but a sibling), but I fail to see the wood for the trees with the above.

Can someone please explain what's going on, and why the long example doesn't work?

Update: NB: The idea was to have a few more if/else branches for different types, but I stopped when the compiler started complaining...

Upvotes: 0

Views: 122

Answers (3)

Hallow
Hallow

Reputation: 1070

The R you are returning is always an R that implements SampleValue of Boolean and not an R that implements SampleValue of T (generic type that is set in runtime).

// if i do this
SampleValueGenerator.forType(Integer.class) 
// i am expecting something that implements SampleValue<Integer>
// but you are always returning something that implements SampleValue<Boolean>

EDIT This should work (did not test yet)

public static <T, R extends SampleValue<T>> R forType(Class<T> clazz) {
    return () -> {
        try{
            return (T)clazz.newInstance(); // Also clazz should have a default constructor.
        }catch(Excepetion e){
            // This catch block should be for NoSuchMethodException and InstantionException
        }
    }
}

Upvotes: 0

Zircon
Zircon

Reputation: 4707

The reason is that your conditional doesn't prove anything to the compiler.

The confusion you're having here involves your conditional:

if(Boolean.class.equals(clazz))

With this check, you're inferring that T is a Boolean, but the compiler has no way of enforcing this. The compiler doesn't implicitly assume that this check will ensure T is Boolean. (All the compiler knows about equals in the context of this method is that it returns a boolean.)

Therefore, despite your check, R and SampleBoolean are incompatible types because R extends SampleValue<T> while T can be anything at all.

I can't really come up with a way to ensure a return of new SampleValue<T> based on T but if I do I will edit this answer with a solution. I'd love to see ideas from others about it.

Upvotes: 2

Valy
Valy

Reputation: 583

I think the problem is that SampleBoolean implements SampleValue<Boolean> which is a specific type and not something generic. On the other hand, R is declared to extend a generic type SampleValue<T>.

SampleValue<T> and SampleValue<Boolean> are two different types, so this is why you get that compilation error. The forType function wants to return a generic type R and you return a specific type with the following statement:

return new SampleBoolean();

Upvotes: 1

Related Questions