Java: fingerprint before generics
This method uses generic methods at the method level that parses values ββfrom a custom POJO, which is JXlistOfKeyValuePairs
exactly what it is. The only thing is that both keys and values ββare in JXlistOfKeyValuePairs
: String
s.
This method wants to accept in addition to an instance of JXlistOfKeyValuePairs
a Class<T>
, which determines what data type should convert the values ββ(assume only Boolean
, Integer
and are possible Float
). It then outputs HashMap
with the specified type for the values ββin its entries.
This is the code I have and it is clearly 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 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 the # isAssignableFrom class.
Any suggestions?
Sample method call:
Map<String, Boolean> foo = fromListOfKeyValuePairs(bar, Boolean.class);
Resolved thanks to both @ Chris Dolan and @polygenelubricants. The reason was that coercion became confusing when combined with autoboxing a primitive. Compiler warnings are excluded because the method parameter clasz
is of type Class<T>
, not just Class
or Class<?>
, so the method call cast
was typical.
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 source to share
You can use the method Class<T>.cast
instead of making your own untested one (T)
.
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 warnings.
As for why the original code doesn't compile, it's because you are trying to cast primitive directly to an unknown reference type. Casting directly from primitive to reference type only works in very specific cases, and in all these 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 casting from a primitive directly to an unknown parameterized type T
(i.e. (T) Integer.parseInt(s)
), so it doesn't compile. It is true that you are trying to write your code in such a way that it is the T
correct type, but there is no way to confirm that at compile time as it T
could be any type at all.
the previous last line bypasses the compile-time error indirectly by casting the primitive to String
after it has already been converted to the reference type Object
. This is why it compiles, although of course it will throw at runtime ClassCastException
.
Here's a typical example with a parameterized type: it's a bit silly, but it repeats the problem of 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
}
Links
a source to share
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 problem was that your code was cheating on autoboxing, so the compiler didn't convert the primitive instance boolean
to boolean
automatically. I've included explicit conversions and voila. The code complains about unsupervised roles, but this is inevitable, so you will want @SuppressWarnings
a source to share