Generics: Compiler seems incapable of recognizing that the type passed to an argument is the same as what is returned - why?

StackOverflow https://stackoverflow.com/questions/3120428

  •  30-09-2019
  •  | 
  •  

Question

Let's say I have several POJOs which all extend a common supertype, BaseObject.

I have a GenericDao which is declared as public interface GenericDao<T>.

For each type-specific DAO, I have an interface which extends the generic type and restricts it to a concrete type (public interface UserDao extends GenericDao<User>) and then an implementation of the type-specific DAO.

In a class that attempts to use a number of GenericDao implementations, I have a method that looks like

public <T extends BaseObject> long create(T object) {
    return getDao(object.getClass()).save(object);
}

If I implement getDao() so that it's parameter is a Class object, such as

private <T extends BaseObject> GenericDao<T> getDao(Class<T> clazz) { ... }

Then the call to getDao(object.getClass() in the create() method fails to compile - the compiler appears to interpret the return type of getDao() as

GenericDao<? extends BaseContractObject>

rather than recognizing that getDao(Class<T>) is going to return me a GenericDao of the same type T.

Can someone explain why this is? I understand that repeated appearances of the same type bound or wildcard don't necessary refer to the same type; however it seems like the compiler should recognize from the signature of getDao(Class<T>) that the T passed in should be the same T returned (but obviously it isn't capable of recognizing this, the why is the part I fail to grasp).

If I instead define getDao's signature to be

private <T extends BaseContractObject> GenericDao<T> getDao(T obj) { ... }

Then there is no issue in compiling a create() implementation which looks like

public <T extends BaseContractObject> long create(T object) {
    return getDao(object).save(object);
}

So why is the compiler capable of recognizing in this case that the T argument passed to getDao(T) is the same T in the return type, whereas it couldn't recognize this when the argument was Class<T>?

Was it helpful?

Solution

The expression object.getClass(), where object is of type T extends BaseObject, returns a Class<? extends BaseObject>, not a Class<T> as one might expect. So, getDao() is returning a DAO of the same type it receives; it's just not receiving the expected type.

OTHER TIPS

This is a classic type erasure issue. getClass() has the following signature:

public final native Class<? extends Object> getClass();

If you have a String and do a getClass() on it, the class you get is Class<? extends String>. The javadocs read:

 * @return The <code>java.lang.Class</code> object that represents
 *         the runtime class of the object.  The result is of type
 *         {@code Class<? extends X>} where X is the
 *         erasure of the static type of the expression on which
 *         <code>getClass</code> is called.

You will need to force the following cast to get it to work:

@SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>)object.getClass();
return getDao(clazz).save(object);

That works for me.

I think this should explain why the constraint is not doing what you expect:

http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ206

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top