Duncan Jones
Duncan Jones

Reputation: 69339

Generic method triggers type safety error - why?

Whilst playing around with solutions for this question, I came up with the following code, which has some compiler warnings. One warning is:

Type safety: The expression of type Test.EntityCollection needs unchecked conversion to conform to Test.EntityCollection<Test.Entity>

I don't entirely understand why this warning appears. By passing in a Class<M> type and declaring the method returns EntityCollection<M>, why am I not doing enough to convince the (Java 7) compiler that the correct type is being returned?

static class Entity {
}

static class EntityCollection<E extends Entity> {

    private EntityCollection(HashMap<?, E> map) {
    }

    public static <T extends HashMap<?, M>, M extends Entity> EntityCollection<M> getInstance(
            Class<T> mapType, Class<M> entityType)
            throws ReflectiveOperationException {

        T map = mapType.getConstructor().newInstance();
        return new EntityCollection<M>(map);
    }
}

public static void main(String[] args) throws Exception {
    // both compiler warnings are on the line below:
    EntityCollection<Entity> collection = EntityCollection.getInstance(
            LinkedHashMap.class, Entity.class);
}

Bonus points if anyone can improve the code to avoid warnings entirely. I've been staring at it for a while and haven't dreamt up any ways to lessen the warnings.

Upvotes: 7

Views: 138

Answers (2)

Adam Arold
Adam Arold

Reputation: 30528

The problem is that getInstance is a generic method but you don't pass generic type parameters to it. You can get around it by passing them like this:

    public static void main(String[] args) throws Exception {
        EntityCollection<Entity> collection = EntityCollection.<LinkedHashMap, Entity>getInstance(
                LinkedHashMap.class, Entity.class);
    }

You will still have to deal a rawtypes warning because LinkedHashMap is a generic type. This is problematic in your case since there is a wildcard in the key type.

You face several problems here:

You can't pass parameterized class objects like this: LinkedHashMap<Object, Entity>.class so you pretty much stuck with the rawtypes warning.

Upvotes: 3

Claudio
Claudio

Reputation: 1858

The problem there is T. You are adding a constraint to your method saying that T should extends HashMap<?, M>. However, the way you are later referencing to T is like a generic parameter of the type Class (Class<T>). LinkedHashMap.class is of type Class<LinkedHashMap> not Class<LinkedHashmap<?, Entity>> (which is what you needed)

A Class object always references a non-parameterized type, and that makes sense. Because the generic binding exists in compile-time, and you are going to use that Class to dynamically reflect the state and behaviour of an instance in runtime. Long story short, you can use a Class<HashMap> to build a new instance, not bounded to any type.

So, I guess what you need to do to your code to modify that constraint in the way it looks like:

public static <T extends HashMap, M extends Entity> EntityCollection<M> getInstance(
            Class<T> mapType, Class<M> entityType)
            throws ReflectiveOperationException {

        T map = mapType.getConstructor().newInstance();
        return new EntityCollection<M>(map);
}

Upvotes: 1

Related Questions