简体   繁体   English

使用反射设置私有字段适用于静态或最终版本,但不适用于静态最终版本(组合)

[英]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. 我的Android项目有两个UnitTest项目。 One for the JUnit Test and one for the Android Unit Tests. 一个用于JUnit测试,另一个用于Android单元测试。 In the JUnit Test Project I've made a class to access or set private fields, methods or constructors. 在JUnit测试项目中,我制作了一个类来访问或设置私有字段,方法或构造函数。 (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.) (PS:对于那些好奇完整代码的人,请告诉我,我将其添加到这篇文章的底部。)

I also have UnitTests to test these private-method accessing. 我也有UnitTests来测试这些私有方法访问。 Right now all of these UnitTests work, accept for one: Setting the value of a final static field. 现在,所有这些UnitTests都可以使用,请接受以下一项: 设置最终静态字段的值。

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: 这是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: 使用以下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): (以及以下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 . 将值设置为字段myInt1myInt2myInt3可以,但是在myInt4我得到了IllegalAccessException

Does anyone know how I should fix this in my setPrivateField method? 有谁知道我应该如何在我的setPrivateField方法中解决此问题? So it can not only set private , private static and private final fields, but also private static final ones. 因此,它不仅可以设置privateprivate staticprivate final场,也是private static final的人。


EDIT 1: 编辑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: 阅读本文禁止Java行为:在运行时更新有关内联的final和static final字段后,我将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());
        }

        // 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): (这是我的getPrivateField方法,该方法已经过全面测试并且可以正常工作):

// 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: 编辑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. 因为我在其上方的UnitTest中使用了getPrivateField并同时测试了我所有的UnitTest,所以它没有用。 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. 当我分别测试上面的UnitTest时,它确实起作用了。.因此,我删除了getPrivateField-UnitTests(因为在上面的代码中,我在一个测试中同时使用了Set和Get),现在它可以工作了。

I know this is very bad practice for UnitTests, but changing a private static final field during RunTime is already bad practice anyway. 我知道这对于UnitTests是非常不好的做法,但是在运行时更改私有static final字段已经是不好的做法了。 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. 我只是制作了一个类来获取和设置私有字段,方法和构造函数,因为在某些UnitTests中它需要大约3-4次,然后我很好奇您可以进行反射多远,并为我的所有内容创建了一个TestCase可以想到的。 (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. 原始静态final字段被特殊对待。

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. static final字段在编译器中是特殊情况,因为可以将其内联到调用它的任何方法中。

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)
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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