views:

714

answers:

5

I've been playing around with reflection in Java... and I'm a little bit baffled.

I was hoping that the program below would allow me to change the value of a public member variable within a class. However, I receive an IllegalArgumentException. Any ideas?

public class ColinTest {

    public String msg = "fail";

    public ColinTest() { }

    public static void main(String args[]) throws Exception {
        ColinTest test = new ColinTest();
        Class c = test.getClass();
        Field[] decfields = c.getDeclaredFields();
        decfields[0].set("msg", "success");

        System.out.println(ColinTest.msg)
    }
}

I receive this message -

Exception in thread "main" java.lang.IllegalArgumentException
    at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:37)
    at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:57)
    at java.lang.reflect.Field.set(Field.java:656)
    at ColinTest.main(ColinTest.java:44)

Thanks.

+3  A: 

The first argument of the Field.set method should be the object which you are reflecting on.

decfields[0].set("msg", "success");

Should read:

decfields[0].set(test, "success");

Furthermore, the final System.out.println call should refer to the test object rather than the class ColinTest, as I presume the intention is to output the contents of the test.msg field.

Update

As pointed out by toolkit and Chris, the Class.getDeclaredField method can be used to specify the name of the field in order to retrieve it:

Field msgField = test.getClass().getDeclaredField("msg");

// or alternatively:

Field msgField = ColinTest.class.getDeclaredField("msg");

Then, the set method of the msgField can be invoked as:

msgField.set(test, "success");

This way has its benefit, as already pointed out by toolkit, if there are more fields added to the object, the order of the fields that are returned by Class.getDeclaredFields may not necessarily return the field msg as the first element of the array. Depending on the order of the returned array to be a certain way may cause problems when changes are made to the class.

Therefore, it would probably be a better idea to use getDeclaredField and declare the name of the desired field.

coobird
Take care using `decFields[0]` if you plan to add more fields in the future!
toolkit
+2  A: 

The first arg to set() should be the object whose field you are changing... namely test.

CurtainDog
+1  A: 

When you are calling getDeclaredFields, each array element will contain a Field object that represents a field on the class, no on an an instantiated object.

That's why you have to specify the object on which you want to set that field, when using the setter:

ColinTest test = new ColinTest();
Field msgfield = ColinTest.class.getDeclaredField("msg");
msgField.set(test, "success");
Christian Hang
Your first arg should be test, not c.
toolkit
Thanks, I saw that, too, right after posting, so I fixed it in an edit right away ;-)
Christian Hang
+1  A: 

What you want is:

Field msgField = c.getDeclaredField("msg");
msgField.set(test, "Success");

Take care using decfields[0] since you may get not get what you expected when you add a second field to your class (you are not checking that decfields[0] corresponds to the msg field)

toolkit
+2  A: 

Please make sure the code you post actually compiles (you want test.msg, not ColinTest.msg).

You may also consider using a newer version of Java, which could provide a more specific error message:

% java ColinTest
Exception in thread "main" java.lang.IllegalArgumentException: Can not set java.lang.String field ColinTest.msg to java.lang.String
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:146)
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:150)
    at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:37)
    at sun.reflect.UnsafeObjectFieldAccessorImpl.set(UnsafeObjectFieldAccessorImpl.java:57)
    at java.lang.reflect.Field.set(Field.java:657)
    at ColinTest.main(ColinTest.java:13)

which would probably have led you to the solution others have posted.

Nicholas Riley