views:

93

answers:

3

This method that uses method-level generics, that parses the values from a custom POJO, JXlistOfKeyValuePairs (which is exactly that). The only thing is that both the keys and values in JXlistOfKeyValuePairs are Strings.

This method wants to taken in, in addition to the JXlistOfKeyValuePairs instance, a Class<T> that defines which data type to convert the values to (assume that only Boolean, Integer and Float are possible). It then outputs a HashMap with the specified type for the values in its entries.

This is the code that I have got, and it is obviously broken.

private <T extends Object>  Map<String, T>
    fromListOfKeyValuePairs(JXlistOfKeyValuePairs jxval, Class<T> clasz)
{
    Map<String, T> val = new HashMap<String, T>();
    List<Entry> jxents = jxval.getEntry();
    T value;
    String str;
    for (Entry jxent : jxents)
    {
        str = jxent.getValue();
        value = null;
        if (clasz.isAssignableFrom(Boolean.class))
        {
            value = (T)(Boolean.parseBoolean(str));
        } else if (clasz.isAssignableFrom(Integer.class))
        {
            value = (T)(Integer.parseInt(str));
        } else if (clasz.isAssignableFrom(Float.class))
        {
            value = (T)(Float.parseFloat(str));
        }
        else {
            logger.warn("Unsupported value type encountered in key-value pairs, continuing anyway: " +
                clasz.getName());
        }
        val.put(jxent.getKey(), value);
    }
    return val;
}

This is the bit that I want to solve:

if (clasz.isAssignableFrom(Boolean.class))
{
    value = (T)(Boolean.parseBoolean(str));
} else if (clasz.isAssignableFrom(Integer.class))
{
    value = (T)(Integer.parseInt(str));
}

I get: Inconvertible types required: T found: Boolean

Also, if possible, I would like to be able to do this with more elegant code, avoiding Class#isAssignableFrom.

Any suggestions?


Sample method invocation:

Map<String, Boolean> foo = fromListOfKeyValuePairs(bar, Boolean.class);

Solved, thanks to both @Chris Dolan and @polygenelubricants. The cause was the typecast was getting confused when combine with the autoboxing of the primitive. Compiler warnings are avoided because the method parameter clasz is of the type Class<T>, instead of just Class or Class<?>, so invoking the cast method was typesafe.

Impl. soln.:

private <T extends Object> Map<String, T> fromListOfKeyValuePairs(
    JXlistOfKeyValuePairs jxval, Class<T> clasz)
{
    Map<String, T> val = new HashMap<String, T>();
    List<Entry> jxents = jxval.getEntry();
    T value;
    String str;
    for (Entry jxent : jxents)
    {
        str = jxent.getValue();
        value = null;
        if (clasz.isAssignableFrom(Boolean.class))
        {
            value = clasz.cast(Boolean.parseBoolean(str));
        }
        else if (clasz.isAssignableFrom(Integer.class))
        {
            value = clasz.cast(Integer.valueOf(Integer.parseInt(str)));
        }
        else if (clasz.isAssignableFrom(Float.class))
        {
            value = clasz.cast((Object)Float.parseFloat(str));
        }
        else
        {
            logger.warn("Unsupporteded value type encountered in key-value pairs, continuing anyway: " +
                clasz.getName());
        }
        val.put(jxent.getKey(), value);
    }
    return val;
}
A: 

Easiest solution is probably to just treat things, in particular the value variable and the map, as Object, and then have a final cast to (T) at the return of the method.

You'll get a bunch of unchecked warnings, but you're going to get those anyway...

Steven Schlansker
+2  A: 

Here's what you want:

    if (clasz.isAssignableFrom(Boolean.class))
    {
        value = (T)Boolean.valueOf(Boolean.parseBoolean(str));
    } else if (clasz.isAssignableFrom(Integer.class))
    {
        value = (T)Integer.valueOf(Integer.parseInt(str));
    } else if (clasz.isAssignableFrom(Float.class))
    {
        value = (T)Float.valueOf(Float.parseFloat(str));
    }

The issue was that your code was fooling the autoboxing, so the compiler was not converting the primitive boolean to a Boolean instance automatically. I put in explicit conversions and voila. The code complains about unchecked casts, but that's unavoidable so you'll want to @SuppressWarnings

Chris Dolan
bguiz
+4  A: 

You can use the Class<T>.cast method instead of doing your own unchecked (T) cast.

    if (clasz.isAssignableFrom(Boolean.class)) {
        value = clasz.cast(Boolean.parseBoolean(str));
    } else if (clasz.isAssignableFrom(Integer.class)) {
        value = clasz.cast(Integer.parseInteger(str));
    } else if (clasz.isAssignableFrom(Float.class)) {
        value = clasz.cast(Float.parseFloat(str));
    }

No compiler warning.


As for why the original code doesn't compile, it's because you're trying to cast a primitive directly to an unknown reference type. Casting directly from primitive to a reference type only works in very specific cases, and in all those cases, the type must be known at compile time.

    Object o;

    o = (Integer) 42; // works! Boxing conversion!
    o = (Number) 42;  // works! Autoboxing then widening reference conversion!
    o = (Object) 42;  // works! Autoboxing then widening reference conversion!
    o = 42; // YES! This also compiles!!!

    o = (String) ((Object) 42); // compiles fine!
    // will throw ClassCastException at run-time

    o = (String) 42; // DOESN'T COMPILE!!!

The last line is analogous to your cast from a primitive directly to an unknown parameterized type T (i.e. (T) Integer.parseInt(s)), which is why it doesn't compile. It's true that you're trying to write the code such that T would be the proper type, but there's no way of confirming that at compile-time, since T can be any type in general.

The previous to last line gets around the compile-time error by indirectly casting the primitive to String, after it had already been converted to an Object reference type. That's why it compiles, although of course it will throw a ClassCastException at run-time.

Here's a parameterized type generic example: it's a bit silly, but reillustrates the problem with casting primitives directly to an unknown reference type:

<T> T f() {
    //return (T) 42; // DOESN'T COMPILE!!!
    return (T) (Integer) 42; // compiles with warning about unchecked cast
}

References

polygenelubricants
+1 @polygenelubricants : Thanks for pointing out how to avoid the compiler warnings. I'll be using this.
bguiz
+check @polygenelubricants : Thanks for the update to your answer - it is much clearer to me now!
bguiz