I have some generic code which I cannot figure out how to legitimately prevent getting warnings from; I am using @SuppressWarnings("unchecked") for the moment, since it seems that casting a generic type can't be done without warnings.
How can I get rid of the annotation?
What I have is:
public MyObject(SharedContext<Object> ctx) {
super(ctx); // set protected field 'context'
...
context.set("Input Fields" ,Collections.synchronizedMap(new TreeMap<String,Pair<String,Boolean>>(String.CASE_INSENSITIVE_ORDER)));
context.set("Output Fields" ,Collections.synchronizedMap(new TreeMap<String,String> (String.CASE_INSENSITIVE_ORDER)));
context.set("Event Registry",new EventRegistry(log) );
}
@SuppressWarnings("unchecked")
protected void startup() {
inputFields =(Map<String,Pair<String,Boolean>>)context.get("Input Fields" ,null);
outputFields =(Map<String,String> )context.get("Output Fields" ,null);
eventRegistry =(EventRegistry )context.get("Event Registry",null);
...
}
The protected variable context is type SharedContext<Object>
.
Without the annotation the compiler gives warnings:
...\MyClass.java:94: warning: [unchecked] unchecked cast
found : java.lang.Object
required: java.util.Map<java.lang.String,com.mycompany.Pair<java.lang.String,java.lang.Boolean>>
inputFields =(Map<String,Pair<String,Boolean>>)context.get("Input Fields" ,null);
^
...\MyClass.java:95: warning: [unchecked] unchecked cast
found : java.lang.Object
required: java.util.Map<java.lang.String,java.lang.String>
outputFields =(Map<String,String> )context.get("Output Fields" ,null);
The Java compiler won't let you cast a generic type across its type parameters because the target type, in general, is neither a subtype nor a supertype.
To use Java generics effectively, you must consider the following restrictions: Cannot Instantiate Generic Types with Primitive Types. Cannot Create Instances of Type Parameters. Cannot Declare Static Fields Whose Types are Type Parameters.
Whenever you want to restrict the type parameter to subtypes of a particular class you can use the bounded type parameter. If you just specify a type (class) as bounded parameter, only sub types of that particular class are accepted by the current generic class.
Advantage of Java Generics They are as follows: 1) Type-safety: We can hold only a single type of objects in generics. It doesn?t allow to store other objects.
After some further research I believe I have found a reasonable alternative, which at least limits the suppression annotation to just one global static utility method to do an unchecked cast.
The self contained test program which follows should be clear enough:
public class Generics
{
static public void main(String[] args) {
Generics.test();
}
static private void test() {
Map<String,Object> ctx=new TreeMap<String,Object>();
Map<String,Object> map=new TreeMap<String,Object>();
Map<String,Object> tst;
ctx.put("Test",map);
tst=uncheckedCast(ctx.get("Test"));
}
@SuppressWarnings({"unchecked"})
static public <T> T uncheckedCast(Object obj) {
return (T)obj;
}
}
Another blog suggested an improvement to this utility method:
@SuppressWarnings("unchecked")
public static <T, X extends T> X uncheckedCast(T o) {
return (X) o;
}
forcing what is returned to be a subclass of the parameter passed in.
Assuming I put uncheckedCast into public utility class GenUtil, my startup method in the question would have no (useless) warnings emitted and look like:
protected void startup() {
inputFields =GenUtil.uncheckedCast(context.get("Input Fields" ,null));
outputFields =GenUtil.uncheckedCast(context.get("Output Fields" ,null));
eventRegistry=GenUtil.uncheckedCast(context.get("Event Registry",null));
...
}
The first unchecked cast can be eliminated by defining a non-generic class that extends the generic Map<String, Pair<String, Boolean>>
and storing that in the SharedContext
instead of a generic TreeMap
, e.g. (using ForwardingMap from Guava):
class InputFieldMap extends ForwardingMap<String,Pair<String,Boolean>> {
private final Map<String,Pair<String,Boolean>> delegate =
Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
protected Map<String,Pair<String,Boolean>> delegate() { return delegate; }
}
// ...
context.set("Input Fields" ,Collections.synchronizedMap(new InputFieldMap()));
// ...
inputFields =(InputFieldMap)context.get("Input Fields" ,null);
outputFields =(Map<?,?> )context.get("Output Fields" ,null);
You could make the second cast safe in the same way, or (assuming you are only reading the map not modifying it) use the map as is (with wildcard parameters) and convert the value to a string with each lookup:
String bar = String.valueOf(outputFields.get("foo"));
or wrap the map:
Map<?, String> wrappedOutputFields =
Maps.transformValues(outputFields, Functions.toStringFunction());
// ...
String bar = wrappedOutputFields.get("foo");
Is the SharedContext object one that you wrote? If so, is it possible to replace the generic String->Object mapping with specific fields?
eg.
context.setInputFields(...)
context.setOutputFields(...)
context.setEventRegistry(...)
context.getInputFields()
etc.
The generic hold-all context object always seems a less-than-perfect solution to me. Especially with generics and the unchecked cast messages that results.
Alternatively, you could create a wrapper object called SoftwareMonkeyContext that has the specific setter/getter methods as above, and internally uses your GenUtil.uncheckedCast method. This would prevent you needing to use GenUtil.uncheckedCast at multiple spots in your code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With