mikeb
mikeb

Reputation: 11307

Java with generics confusion

I cannot figure out the proper way to do this with generics. I have a class, Foo, which has a generic (Foo<class>). I then want to have a map of Map<String, Foo>. Now, I can do this, and add Foo<String> as one map item, and Foo<Integer> as another. However, when I use the map's get method, I just get a Foo back and the type can no longer be inferred, so if I do:

String s = map.get("StringFoo")

I get a compile error and have to do:

String s = (String) map.get("StringFoo")

What is a good pattern for doing something like this to avoid the casting, since that is what generics are for in the first place. I may be able to do something like Map<String, Foo<?>>, but is that the way to do it?

Details of my code follow, this can be put into a directory and javac *.java && java Main will run it.

I have a generic java class in Foo.java,

public class Foo<T>
{
    T value;

    public T getValue()
    {
        return this.value;
    }

    public void setValue(T t)
    {
        this.value = t;
    }
}

Now, I have the following test class in Main.java:

import java.util.Map;
import java.util.HashMap;

public class Main
{
    public static void main(String[] a)
        {
            Foo<String> fooStr = new Foo<String>();
            fooStr.setValue("TEST 123");

            Foo<Integer> fooInt = new Foo<Integer>();
            fooInt.setValue(314159);

            Map<String, Foo> mapOfFoo = new HashMap<String, Foo>();

            mapOfFoo.put("Strings", fooStr);
            mapOfFoo.put("Integer", fooInt);

            System.out.println("All set");

            String s = mapOfFoo.get("Strings").getValue();

            System.out.println("Got: " + s);

        }
}

When I compile this, I get the following error:

Main.java:21: error: incompatible types
            String s = mapOfFoo.get("Strings").getValue();
                                                       ^
  required: String
  found:    Object
1 error

When I do this in Main.java, it works:

import java.util.Map;
import java.util.HashMap;

public class Main
{
    public static void main(String[] a)
        {
            Foo<String> fooStr = new Foo<String>();
            fooStr.setValue("TEST 123");

            Foo<Integer> fooInt = new Foo<Integer>();
            fooInt.setValue(314159);

            Map<String, Foo> mapOfFoo = new HashMap<String, Foo>();

            mapOfFoo.put("Strings", fooStr);
            mapOfFoo.put("Integer", fooInt);

            System.out.println("All set");

            String s = (String)mapOfFoo.get("Strings").getValue();

            System.out.println("Got: " + s);

        }
}

I'm not sure what the best practice is for something like this. Does anyone have any suggestions?

Upvotes: 0

Views: 378

Answers (2)

torquestomp
torquestomp

Reputation: 3344

This kind of construct is actually not uncommonly needed in Java, the problem is that generic parameters are a compile-time-only feature, and that map-retrieval is a run-time-only feature, so there's no way for the compiler to understand your rule and not raise a warning about it somewhere.

The safest way to implement a structure like this is through a wrapper class, which logically guarantees type safety, and all you have to do is throw a SuppressWarnings("unchecked") on the getter.

public class FooMapper {

  private Map<Class<?>, Foo<?>> fooMap = new HashMap<>();

  public <T> void setFoo(Class<T> clazz, Foo<T> foo) {
    fooMap.put(clazz, foo);
  }

  @SuppressWarnings("unchecked")
  public <T> Foo<T> getFoo(Class<T> clazz) {
    return (Foo<T>) fooMap.get(clazz);
  }

}

This wrapper guarantees that you can never put a Foo of the wrong type with the wrong Class object, so the 'unchecked' cast can never possibly fail.

Upvotes: 3

Hovercraft Full Of Eels
Hovercraft Full Of Eels

Reputation: 285430

You appear to be using a non-generic Foo in your Map declaration and construction. Instead this:

Map<String, Foo> mapOfFoo = new HashMap<String, Foo>();

Should be this:

Map<String, Foo<String>> mapOfFoo = new HashMap<String, Foo<String>>();

Then you won't have to cast to String -- one of the reasons to use generics in the first place, and Foo#getValue() will return a String unequivocally.

Upvotes: 4

Related Questions