簡體   English   中英

使用反射設置私有字段適用於靜態或最終版本,但不適用於靜態最終版本(組合)

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

我的Android項目有兩個UnitTest項目。 一個用於JUnit測試,另一個用於Android單元測試。 在JUnit測試項目中,我制作了一個類來訪問或設置私有字段,方法或構造函數。 (PS:對於那些好奇完整代碼的人,請告訴我,我將其添加到這篇文章的底部。)

我也有UnitTests來測試這些私有方法訪問。 現在,所有這些UnitTests都可以使用,請接受以下一項: 設置最終靜態字段的值。

這是我用於設置私有字段的方法:

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

這是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);
    }
}

使用以下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;
    }
}

(以及以下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;
    }
}

將值設置為字段myInt1myInt2myInt3可以,但是在myInt4我得到了IllegalAccessException

有誰知道我應該如何在我的setPrivateField方法中解決此問題? 因此,它不僅可以設置privateprivate staticprivate final場,也是private static final的人。


編輯1:

閱讀本文禁止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);
    }
}

(這是我的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;
}

但是我仍然遇到同樣的錯誤。


編輯2:

因為我在其上方的UnitTest中使用了getPrivateField並同時測試了我所有的UnitTest,所以它沒有用。 當我分別測試上面的UnitTest時,它確實起作用了。.因此,我刪除了getPrivateField-UnitTests(因為在上面的代碼中,我在一個測試中同時使用了Set和Get),現在它可以工作了。

我知道這對於UnitTests是非常不好的做法,但是在運行時更改私有static final字段已經是不好的做法了。 我只是制作了一個類來獲取和設置私有字段,方法和構造函數,因為在某些UnitTests中它需要大約3-4次,然后我很好奇您可以進行反射多遠,並為我的所有內容創建了一個TestCase可以想到的。 (我個人覺得有點太遠了。)

警告:除測試外,請勿在其他任何情況下使用反射。 我不建議您在常規項目中使用它,除非您嘗試了其他所有可能的方法。 (我什至無法想到除了測試之外,您還想在項目中使用反射的情況。)

原始靜態final字段被特殊對待。

編譯器將其內聯為常量。 最終的可執行文件不再在運行時訪問該字段。

static final字段在編譯器中是特殊情況,因為可以將其內聯到調用它的任何方法中。

它甚至可能不存在於最終代碼中。

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