views:

119

answers:

4

In my utility method:

public static <T> T getField(Object obj, Class c, String fieldName) {
    try {
        Field field = c.getDeclaredField(fieldName);
        field.setAccessible(true);
        return (T) field.get(obj);
    } catch (Exception e) {
        e.printStackTrace();
        fail();
        return null;
    }
}

The line

return (T) field.get(obj);

gives the warning "Type safety: Unchecked cast from Object to T"; but I cannot perform instanceof check against type parameter T, so what am I suppose to do here?

+2  A: 

The annotation @SuppressWarnings will stop the compiler reporting this warning. I don't think there's any way you can get away from the compiler warning when using reflection like this. Something like the following:

Field field = c.getDeclaredField(fieldName);
field.setAccessible(true);

@SuppressWarnings(value="unchecked")
T t = (T) field.get(obj);

return t;
Jeff Foster
You can specify `Class<T>` (for non-generic `T`) as mentioned in other answers, but there isn't really any point in test code. Generics on reflection are really just a helping hand - they aren't really correct (see `Object.getClass`), and that doesn't matter because when you are using reflection you are blowing away type safety anyway.
Tom Hawtin - tackline
A: 

Generics are there to provide type safety in places where you didn't previously have any in Java. So it used to be that if you had a list full of Strings you had to do:

   String myString = (String)myList.get(0); 

but now you can retrieve it without casting it:

   String myString = myList.get(0);   //Compiler won't complain

When you generify using the variable T, you are saying T is a placeholder for a specific type, which will be defined on the instance of the class at instantiation time. For instance:

public class ArrayList<T> {
  public ArrayList<T> {
    ....
  }
}

allows you to instantiate the list with:

 ArrayList<String> myList = new ArrayList<String>();

Now every function on ArrayList will return a String, and the compiler knows this so it doesn't require a cast. Each of those functions was defined much like yours above:

   public T get(int index);
   public void set(int index, T object);

at compile time they become:

   public String get(int index);
   public void set(int index, String object);

In your case, however, you seem to be trying to use T as a wildcard, which is different from a placeholder for a specific type. You might call this method three times for three different fields, each of which has a different return type, right? This means that, when you instantiate this class, you cannot pick a single type for T.

In general, look at your method signatures and ask yourself "will a single type be substituted for T for each instance of this class"?

    public static <T> T getField(Object obj, Class c, String fieldName)

If the answer is "no", that means this is not a good fit for Generics. Since each call will return a different type, you have to cast the results from the call. If you cast it inside this function, you're losing any benefits Generics would provide, and might as well save yourself the headaches.

If I've misunderstood your design, and T does refer to a single type, then simply annotating the call with @SuppressWarnings(value="unchecked") will do the trick. But if I've understood correctly, fixing this error will just lead you to a long road of confusion unless you grok what I've written above.

Good luck!

Benjamin Cox
+1  A: 

You can easily solve this problem by adding an additional parameter to your method which will specify the type of the filed, the method will then look as follows:

  public static <T> T getField(Class<T> fieldType, Object obj, Class<?> c, 
    String fieldName) 
  {
     try {
         Field field = c.getDeclaredField(fieldName);
         field.setAccessible(true);
         Object value = field.get(obj);
         return fieldType.cast(value);
     } catch (Exception e) {
         e.printStackTrace();
         fail();
         return null;
     }
 }

And here's how you can use it: getField(String.class, new G(), G.class, "s") where G is defined as:

 public class G {
  String s = "abc";      
 }

A 2nd improvement is to eliminate the c parameter of getFiled(). c can be obtained inside the method by invoking obj.getClass(). The only caveat is that this will give you the dynamic type of the object so you mat want to loop over all of C's superclasses until you find the field you're looking for, or until you arrive at Object (You will also need to use c.getFields() and look for the field in the resulting array).

I think that these changes will make your method easier to use and less prone to errors so it's worth the effort.

Itay
A: 

As suggested above, you can specify the expected type of the field and call the cast method.

Also. you don't need to pass argument object's class. You can find out what it is by calling obj.getClass()

This simplifies your code to

public static <T> T getField(Object obj, Class<T> fieldClass, String fieldName) {
    try {
        Class<?> declaringClass = obj.getClass();
        Field field = declaringClass.getDeclaredField(fieldName);
        field.setAccessible(true);
        return fieldClass.cast(field.get(obj));
    }
    catch (Exception e) {
        throw new AssertionFailedError();
    }
}
armandino