Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Warnings in Java when casting to a generic type

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);
like image 490
Lawrence Dol Avatar asked Feb 20 '10 04:02

Lawrence Dol


People also ask

Can you cast to generic in Java?

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.

What is not allowed for generics Java?

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.

How do I restrict a generic type in Java?

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.

What is type safety in Java generics?

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.


3 Answers

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));
    ...
    }
like image 121
Lawrence Dol Avatar answered Sep 21 '22 18:09

Lawrence Dol


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");
like image 37
finnw Avatar answered Sep 20 '22 18:09

finnw


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.

like image 20
Steve McLeod Avatar answered Sep 18 '22 18:09

Steve McLeod