7

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.

Community
  • 1
  • 1
Duncan Jones
  • 67,400
  • 29
  • 193
  • 254
  • Hmm, with a bit more experimentation I can't get either Java 7 or Java 8 to object to this (except for a "you could use diamond inference" on the `return new EntityCollection(map)`). Other than that its very happy. I'm using netbeans – Richard Tingle May 20 '14 at 14:20
  • @RichardTingle I can get Java 1.8.0_05 to give me warnings if I compile with `javac -Xlint:all` on the command line. – Duncan Jones May 20 '14 at 14:21

2 Answers2

3

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.

Adam Arold
  • 29,285
  • 22
  • 112
  • 207
  • Thanks for this explanation. Note that my `Class entityType` argument was redundant and I could happily remove this after making your modifications. – Duncan Jones May 20 '14 at 14:40
  • So you main problem is that each invocation of this method produces a warning and you are willing to suppress them if you move them into the method? – Adam Arold May 20 '14 at 14:46
  • Mostly I was interested in understanding why that particular warning was received, hence you've got my accepted answer. Claudio's answer might prove more useful in the long term, due to the easier way the warnings can be suppressed. – Duncan Jones May 20 '14 at 14:47
  • Note that you will get a `rawtypes` warning each time you reference any derived class of `HashMap` without the type parameters. This will be of course outside the `getInstance` method. – Adam Arold May 20 '14 at 14:49
1

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);
}
Claudio
  • 1,848
  • 12
  • 26
  • I like how this pulls the warnings into the factory method (where they can be suppressed with `@SuppressWarnings({ "rawtypes", "unchecked" })`) rather than revealing them to the caller. – Duncan Jones May 20 '14 at 14:44
  • Yes, I don't completely remove warnings from here. Generics are way too rigid in Java and I think they fall short in things like this. However the question is, do we really need more? Encapsulating the warning on the factory method is good enough and does not add any extra complexity. – Claudio May 20 '14 at 14:50