views:

423

answers:

5

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);
A: 

How is the context variable being assigned? Is it from the parameter ctx, which is of type :

SharedContext<Object>

?

If so, thats your problem because when you do a get you haven't effectively typed what you are getting.

Amir Afghani
Yes it is. I can't type what I am getting beyond Object because each entry in the context is necessarily of a different type.
Software Monkey
Is there a shared common type to all of them?
Amir Afghani
If not, try passing in SharedContext<?> ctx
Amir Afghani
@Amir: Seriously? Sure I'll do that.
Software Monkey
I don't think it will get rid of your warning, but I think its more proper than SharedContext<Object> - let me know I'm waiting here to see what you report.
Amir Afghani
@Amir: That gave me compiler errors when setting the values into the context: `cannot find symbol` in `location: class com.mycompany.SharedContext<capture of ?>`
Software Monkey
Another question, how is inputPairs declared?
Amir Afghani
Why is it more proper that Object? I have a collection of things that can be any object - thus `SharedContext<Object>`.
Software Monkey
You mean `inputFields`? Exactly as cast on extraction - I copy/pasted the cast. You can see it's creation in the constructor.
Software Monkey
I have to step away for a few hours, I'll be back to see what we learn from this.
Amir Afghani
@Software Monkey -- you have a collection of unknown things, so using "?" is a placeholder for saying "I have a collection of stuff" and IIRC is preferred here.
Mark E
@Amir, @Mark: Did some further reading, and in this case <?> is wrong; the objects receiving the context specifically require a context into which they can put, and from which they can extract, any object by name. And, by design, one can't put *anything* into a Generic of unknown type <?>, you can only take things out of it.
Software Monkey
A: 

Which version of compiler you are using? With Java 6 compiler(Sun JDK windows) I did not see detailed warning. I am getting warning information only when I use '-Xlint:unchecked' flag.

Try -Xlint:-unchecked and let us know if it resolves your issue. For more on flags,

http://java.sun.com/javase/6/docs/technotes/tools/windows/javac.html

Adi
Yes, I am using -Xlint:unchecked - I want to see such warnings. What I want here is not just to make it go away by disabling (I can do that with the annotation), but to make it go away by correctly indicating my intention to the compiler.
Software Monkey
+2  A: 

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));
    ...
    }
Software Monkey
Personally, I'd consider `GenUtil.uncheckedCast` a worse evil than supressing a specific warning: There is no guarantee that this utility method is used responsibly, i.e. in a way that does not cause heap pollution, but programmers are no longer prompted by a warning to attempt a more robust implementation. (Which is sometimes, though not always, possible ...)
meriton
I disagree - any cast is in essence informing the compiler "I know what the type of this is and you don't, so get out of my way and take my word for it". The warning emitted in this circumstance just should not be there - it's like the compiler is saying, "I know I don't know, but I'm not convinced you do either".
Software Monkey
The compiler doesn't take your word, it emits a runtime check. A cast can be (and sometimes is) used as assertion about an object's type. The warning tells you that this check is incomplete and hence can not be relied on.
meriton
@Meriton: By the way, after gaining more experience, I understand what you mean by incomplete, and I agree; in fact the check on the actual generic types *in* the collection will be deferred until some time later, perhaps much later... but that seems unavoidable in this case and, in general, when mixing old and new collections. To me using uncheckedCast() effectively says "I tried all the better alternatives and they can't work in this case".
Software Monkey
+1  A: 

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.

Steve McLeod
+1  A: 

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");
finnw
A good answer, and interesting. However, don't you think it somewhat defeats the whole purpose of generics if I have to define a non-generic subclass? After all the benefit of `Map<String,String>` is that I *don't* have to create `MapStringString extends TreeMap` in order to create a map of String -> String.
Software Monkey
Not quite. If you wanted to define a `MapStringString` class you would still benefit from generics. Try writing `MapStringString` in Java 1.4 and you'll see what I mean.
finnw