tags:

views:

1145

answers:

6

So I have a map:

Map<String, Class> format = new HashMap<String, Class>();

And I would add elements to it like this:

format.put("Vendor Number", Integer.class);
format.put("Vendor Dispatch", Date.class); 
....

I have a generic method as follows:

public static <T> T verifyType(String name, Class<T> type) {
 if (type == Integer.class) {
  return type.cast(new Integer(Integer.parseInt(name)));
 }
             ......
 return null;
}

Now this piece of code works great with no compiler issues:

Integer i = verifyType("100",Integer.class);

But, when I try this:

    Integer i = verifyType("100",format.get("Vendor Number"));

OR 

    Class type = Integer.class
    Integer i = verifyType("100",type);

Compiler shows me this warning: Type safety: Unchecked invocation verifyType(String,Class) of the generic method verifyType(String, Class)

That leaves me puzzled... please help...

+1  A: 

It is because your HashMap returns a

Class

object, but the verify function expects a

Class<T>

object.

Nathaniel Flath
well...I did try this out before asking my question here:Map<String, Class<T>> format = new HashMap<String, Class<T>>();only to be informed by the compiler that "T cannot be resolved as a type".
Jay
@Jay, The problem with Class<T> is you have to have a defined parameter. What you want is Class<?>.
Yishai
@Yishai - Thanks, that did it. However, the compiler complains about the return on verifyType method: \npublic static <T> T verifyType(String value, Class<T> type) throws LKNException {if (value != null) { if (type == Integer.class) { try { return type.cast(new Integer(value)); } catch (Exception err) { }}
Jay
gosh! how do I add new lines on comments!!
Jay
+3  A: 

Change:

Class type = Integer.class
Integer i = verifyType("100",type);

to

Class<Integer> type = Integer.class
Integer i = verifyType("100",type);

By only declaring the type as 'Class', you're losing the generic parameter and the verifyType() method can't infer the class, thus the unchecked warning.

This problem:

Map<String, Class> format = new HashMap<String, Class>();
format.put("Vendor Number", Integer.class);
format.put("Vendor Dispatch", Date.class);
Integer i = verifyType("100",format.get("Vendor Number"));

can't really be solved due to type erasure. The compiler can't infer the type based on a generic parameter that is gone by runtime. This is because Java generics are little more than smoke and mirrors for casting.

cletus
This works, however, obviously, this wont supress the warning if I were to obtain Class from the declared hashmap.
Jay
A: 

Class Class is a generic class itself. If you try

Class<Integer> type = Integer.class;
Integer i = verifyType("100", type);

it should work better. Generification of the input parameter convinces the compiler that you know what you're doing, enough to let your code compile without warning.

You can also convince the compiler by suppressing the warning using

@SuppressWarning("unchecked")

before the specific line of code or method.

Yuval
+1  A: 

The reason you get this error is that in the first case the compiler sees that you pass it a Class object and is able to bind T to Integer at compile time, but in the second case the compiler only sees that you are passing it a Class object.

At the end of the day, you will not be able to do

Integer i = verifyType("100",format.get("Vendor Number"));

in a type-safe way, since the compiler can't know that you will get an Integer (what if someone does a format.put("Vendor Number", X.class) just before that call?)

waxwing
+2  A: 

You have to genericize your references to Class. For example:

Class<Integer> type = Integer.class
Integer i = verifyType("100",type);

would work fine.

As would:

Map<String, Class<?>> format = new HashMap<String, Class<?>>();

However, this will never work:

Integer i = verifyType("100",format.get("Vendor Number"));

Because format is not defined as:

Map<String, Class<Integer>>

If it was, the casting would work, but the design would be pointless.

The closest you can get is:

Integer i = verifyType("100",(Class<Integer>) format.get("Vendor Number"));

However, you will get a compiler warning doing it, as you must - it is an inherently unsafe cast. The compiler is taking your word for it that that format.get statement will return an integer. If you are sure of that, then that is what unsafe casts are for. If you want to get rid of the compiler warning, you could do this:

    Class<?> type = format.get("Vendor Number");
    Integer i = null;
    if (type == Integer.class) {
        i = verifyType("100", Integer.class);
    } else {
        //What do you want to do?
    }
Yishai
Instead of Integer i = verifyType("100",(Class<Integer>) format.get("Vendor Number"));you can write: Integer i = (Integer) verifyType("100", format.get("Vendor Number"));Less typing.
Tadeusz Kopec
+1  A: 

It is strange for me, that your compiler on line

Integer i = verifyType("100",format.get("Vendor Number"));

complains about unchecked invocation but doesn't show an error like "Type mismatch: cannot convert from Object to Integer".

This line just doesn't make sense to me. What do you expect verifyType to return if it receives Date.class as a second parameter? Also an Integer (then the return type of verifyType is invalid)? Or a Date? If you expect a Date, how is compiler expected to know that

format.get("Vendor Number");

returns Integer.class, but

format.get("Vendor Dispatch");

returns Date.class

Invocation of

Integer i = verifyType("100",format.get("Vendor Dispatch"));

must fail, as Date is not an Integer. So, in my opinion (and in opinion of my compiler) compilation of

Integer i = verifyType("100",format.get("Vendor Number"));

must fail.

Tadeusz Kopec
tkopec, I beg to differ. I realize that what I am trying to do is no different from adding different types of objects to a list (like in java1.4) - I would think that the compiler would warn at compile time (which it is doing right now), and throw a class cast exception at run time. It wouldn't make sense for it to fail on compilation.
Jay
Huh? If you take an element from a plain List, you have to cast it. You can't write Integer i = list.get(0);It must beInteger i = (Integer) list.get(0);Your example is the same case - to compile it must beInteger i = (Integer) verifyType("100",format.get("Vendor Number"));And to get rid of warning change type of format variable toMap<String, Class<?>> format = new HashMap<String, Class<?>>();
Tadeusz Kopec
@tkopec point taken. The compiler does suppress warnings if I declare it as Class<?>.So, what do I return from my verifyType(String val, Class<T> type> method? Object??
Jay