简体   繁体   中英

Set private field with reflection works on static OR final, but not static final (combined)

I've got two UnitTest projects for my Android project. One for the JUnit Test and one for the Android Unit Tests. In the JUnit Test Project I've made a class to access or set private fields, methods or constructors. (PS: For the ones that are curious of the complete code, let me know and I'll add it to the bottom of this post.)

I also have UnitTests to test these private-method accessing. Right now all of these UnitTests work, accept for one: Setting the value of a final static field.

This is the method I use for setting a private field:

// Test method to set a private Field from a class
public static void setPrivateField(Object ob, String fieldName, Object value) throws MyUnitTestException{
    try {
        Field field = ob.getClass().getDeclaredField(fieldName);
        if(field != null){
            field.setAccessible(true);
            if(Modifier.isFinal(field.getModifiers())){
                Field modifierField = Field.class.getDeclaredField("modifiers");
                modifierField.setAccessible(true);
                modifierField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

                /*int modifiers = field.getModifiers();
                Field modifierField = field.getClass().getDeclaredField("modifiers");
                modifiers = modifiers & ~Modifier.FINAL;
                modifierField.setAccessible(true);
                modifierField.setInt(field, modifiers);*/
            }
            // ** IllegalAccessException at the following line with final static fields:
            field.set(ob, value);  // static fields ignore the given Object-parameter
        }
    }
    catch (NoSuchFieldException ex){
        throw new MyUnitTestException(ex);
    }
    catch (IllegalAccessException ex){
        throw new MyUnitTestException(ex);
    }
    catch (IllegalArgumentException ex){
        throw new MyUnitTestException(ex);
    }
}

And this is the UnitTest:

@Test
public void testSetIntFields(){
    MyClass myClassInstance = new MyClass();
    final int value = 5;
    for(int nr = 1; nr <= 4; nr++){
        String nameOfField = "myInt" + nr;

        try {
            TestMethodsClass.setPrivateField(myClassInstance, nameOfField, value);
        }
        catch (MyUnitTestException ex) {
            Assert.fail("setPrivateField caused an Exception: " + ex.getThrownException());
        }

        int x = myClassInstance.getMyInt(nr);

        Assert.assertTrue("myInt " + nr + " should be above 0", x > 0);
        Assert.assertEquals("myInt " + nr + " should equal the set value (" + value + ")", value, x);
    }
}

With the following MyClass:

@SuppressWarnings("unused")
public class MyClass
{
    private int myInt1 = 0;
    private static int myInt2 = 0;
    private final int myInt3 = 0;
    private static final int myInt4 = 0;

    public MyClass(){ }

    public int getInt(int nr){
        switch(nr){
            case 1:
                return myInt1;
            case 2:
                return myInt2;
            case 3:
                return myInt3;
            case 4:
                return myInt4;
        }
        return -1;
    }
}

(And the following MyUnitTestException):

public class MyUnitTestException extends Exception
{
    private static final long serialVersionUID = 1L;

    private Throwable thrownException;

    public MyUnitTestException(Throwable ex){
        super(ex);
        thrownException = ex;
    }

    public String getThrownException(){
        if(thrownException != null)
            return thrownException.getClass().getName();
        else
            return null;
    }
}

Setting the value to the fields myInt1 , myInt2 and myInt3 works, but at myInt4 I'm getting an IllegalAccessException .

Does anyone know how I should fix this in my setPrivateField method? So it can not only set private , private static and private final fields, but also private static final ones.


EDIT 1:

After reading this article Forbidden Java actions: updating final and static final fields about in-lining at RunTime, I modified my UnitTest to this:

@Test
public void testSetIntFields(){
    MyClass myClassInstance = new MyClass();
    final int value = 5;
    for(int nr = 1; nr <= 4; nr++){
        String nameOfField = "myInt" + nr;

        try {
            TestMethodsClass.setPrivateField(myClassInstance, nameOfField, value);
        }
        catch (MyUnitTestException ex) {
            Assert.fail("setPrivateField caused an Exception: " + ex.getThrownException());
        }

        // Get the set value using reflection
        // WARNING: Since at RunTime in-lining occurs, we never use a Getter to test the set value, but instead use reflection again
        int x = -1;
        try {
            x = (Integer)TestMethodsClass.getPrivateField(myClassInstance, nameOfField);
        }
        catch (MyUnitTestException ex) {
            Assert.fail("getPrivateField caused an Exception: " + ex.getThrownException());
        }

        Assert.assertTrue("myInt " + nr + " should be above 0", x > 0);
        Assert.assertEquals("myInt " + nr + " should equal the set value (" + value + ")", value, x);
    }
}

(And this is my getPrivateField method, which is already completely tested and works):

// Test method to access a private Field from a class
public static Object getPrivateField(Object ob, String fieldName) throws MyUnitTestException{
    Object returnObject = null;
    try {
        Field field = ob.getClass().getDeclaredField(fieldName);
        if(field != null){
            field.setAccessible(true);
            returnObject = field.get(ob); // static fields ignore the given Object-parameter
        }
    }
    catch (NoSuchFieldException ex) {
        throw new MyUnitTestException(ex);
    }
    catch (IllegalAccessException ex) {
        throw new MyUnitTestException(ex);
    }
    catch (IllegalArgumentException ex) {
        throw new MyUnitTestException(ex);
    }
    return returnObject;
}

But I still get the same error.


EDIT 2:

Because I was using a getPrivateField in a UnitTest above it and tested all my UnitTests at the same time, it didn't worked. When I tested the UnitTest above separately it did work.. So I removed the getPrivateField-UnitTests (since in the code above I use both the Set and Get in one test) and now it does work.

I know this is very bad practice for UnitTests, but changing a private static final field during RunTime is already bad practice anyway. I just made the class to get and set private fields, methods and constructors, because I needed it about 3-4 times in some of my UnitTests and then I was just curious how far you can go with reflection and created a TestCase for everything I could think of. (Personally I find it a bit too far though.)

WARNING: Do not use reflection in any other case than tests. I don't recommend using it in your normal project, unless you've tried every other possible way. (I can't even think of a situation where you'd want to use reflection in your project, apart from tests.)

A primitive static final field is treated specially.

It is inlined as a constant by the compiler. The final executable does not access the field at runtime anymore.

A static final field is special-cased in the compiler, as its allowed to be inlined into any method that calls it.

It may not even exist in the end code.

static final int TEN = 10; // removed at compile time

int twenty() {
  return TEN * 2; // compiler will turn this into 10*2 (and then probably 20 directly)
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM