colinjwebb
colinjwebb

Reputation: 4378

IllegalArgumentException when setting public member

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.

Upvotes: 2

Views: 18129

Answers (6)

swdev
swdev

Reputation: 5157

I stumbled accros this page, because weirdly, I can not sets public string field in my class. The code will add new row in ArrayList in each for loop. The problem is, I put instantiation code of new object (using reflection that is) only once, outside the inner for.

private ArrayList processDataSetResultSetAsArrayList(ResultSet resultSet, String fqnModel) {
    ArrayList result = new ArrayList();

    try {
        ResultSetMetaData metaData;
        int nColoumn;
        String columnName;
        String fieldValue;
        Field field;
        Object modelInstance;

        metaData = resultSet.getMetaData();
        nColoumn = metaData.getColumnCount();
        resultSet.beforeFirst();
        Class modelClass = Class.forName(fqnModel);
        while (resultSet.next()) {
            modelInstance = modelClass.newInstance();
            for (int i = 1; i <= nColoumn; i++) {
                columnName = metaData.getColumnName(i);
                field = modelInstance.getClass().getDeclaredField(columnName);
                fieldValue = resultSet.getString(i);
                field.set(modelInstance, fieldValue);
            }
            result.add(modelInstance);
        }            
    } catch (Exception ex) {
        Logger.getLogger(DB.class.getName()).log(Level.SEVERE, null, ex);
    }
    return result;
}

Inspect that now I move the Class.forName(fqnModel) outside the while loop. Because surely we only need to create Class object only once. But then, before each for loop, I create a model object that eventually will be added in a ArrayList.

To be clear, this is my BiroModel class look like:

public class BiroModel extends Model {
public String idbiro = "";
public String biro = "";

public BiroModel() {
}

public BiroModel(String table, String pkField) {
    super(table, pkField);
    fqn = BiroModel.class.getName();

}

public String getBiro() {
    return biro;
}

public void setBiro(String biro) {
    this.biro = biro;
}

public String getIdbiro() {
    return idbiro;
}

public void setIdbiro(String idbiro) {
    this.idbiro = idbiro;
}

}

I create a convention here, that all field object should be declared public. But, because I need EL syntax, I still need to create getter/setter for this public field.

Upvotes: 0

coobird
coobird

Reputation: 160964

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.

Upvotes: 8

toolkit
toolkit

Reputation: 50237

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)

Upvotes: 1

Nicholas Riley
Nicholas Riley

Reputation: 44321

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.

Upvotes: 2

Christian Hang-Hicks
Christian Hang-Hicks

Reputation: 2115

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");

Upvotes: 1

CurtainDog
CurtainDog

Reputation: 3205

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

Upvotes: 2

Related Questions