当前位置: 首页 > 知识库问答 >
问题:

设置私有字段与反射工程静态或最终,但不是静态最终(组合)

单于楚
2023-03-14

我有两个UnitTest项目为我的Android项目。一个用于JUnit测试,一个用于Android Unit测试。在JUnit测试项目中,我创建了一个类来访问或设置私有字段、方法或构造函数。(附言:对于那些对完整代码感到好奇的人,请告诉我,我会把它添加到这篇文章的底部。)

我也有UnitTets来测试这些私有方法的访问。现在,所有这些UnitTest都可以工作,接受一个:设置final static字段的值。

这是我用来设置私有字段的方法:

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

这是单元测试:

@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我得到一个IllegalAccessExc0019。

有人知道我应该如何在setPrivateField方法中解决这个问题吗?因此,它不仅可以设置privateprivate staticprivate final字段,还可以设置private static final字段。

编辑1:

在阅读了本文《禁止Java操作:在运行时更新关于in-lining的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:

因为我在上面的单元测试中使用了getPrivateField,并且同时测试了我的所有单元测试,所以它不起作用。当我单独测试上面的UnitTest时,它确实起了作用。。因此,我删除了getPrivateField单元测试(因为在上面的代码中,我在一个测试中同时使用Set和Get),现在它可以工作了。

我知道这对于UnitTests来说是非常糟糕的做法,但是在运行时更改私有静态final字段已经是糟糕的做法了。我只是创建了一个类来获取和设置私有字段、方法和构造函数,因为在我的一些单元测试中,我需要它3-4次,然后我只是好奇反射可以走多远,并为我能想到的一切创建了一个html" target="_blank">测试用例。(但就我个人而言,我觉得这有点太离谱了。)

警告:不要在测试以外的任何情况下使用反射。我不建议在您的正常项目中使用它,除非您已经尝试了所有其他可能的方法。(除了测试之外,我甚至想不出在项目中使用反射的情况。)

共有2个答案

鱼浩荡
2023-03-14

静态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)
}
宰父智敏
2023-03-14

原始静态最终字段被特殊处理。

编译器将其作为常量内联。最终可执行文件不再在运行时访问该字段。

 类似资料:
  • 问题内容: 基于使用Java反射更改私有静态最终字段,我尝试设置私有静态最终字段。 (我知道这非常骇人,但是这个问题与代码质量无关;与Java反射有关。) 此打印 我已经使用OpenJDK 6和7,以及Oracle 7进行了尝试。 我不知道Java反射可以提供什么保证。但是,如果失败了,我以为会有一个(实际上所有反射方法都会抛出异常)。 这是怎么回事 问题答案: Java内联字段可在编译时初始化为

  • 问题内容: 我有一堂课,但不幸的是,我需要在运行时更改它。 使用反射我得到这个错误: 有什么办法可以改变价值? 问题答案: 假设没有阻止你执行此操作,则可以使用来绕开并重置修饰符以摆脱,并实际上修改字段。 这是一个例子: 假设没有SecurityException抛出,上面的代码将打印出来”Everything is true”。 实际执行的操作如下: 基本boolean值true和falsein

  • 使用Java反射更改私有静态final字段 我按照上面链接中的说明使用java反射更改私有静态final字段。我有一个名为“数据”的对象在“data”内部,有一个名为“type”的私有静态最终变量我想将“type”设置为null。这是我的密码。 我试着在Java1.7上用类似的代码来做这件事,结果成功了。但是在Android上运行此代码会产生以下错误:java。lang.NoSuchFieldEx

  • 我们正在开发基于spring boot 2.5.6的支付应用程序。该应用程序托管在docker上,并在Java14上启动。我们创建了一个名为的抽象类,它是,包含两个变量和。所有枚举类都扩展了这个基类,并在其中定义常量。 这个决定是为了定制整个应用程序层中使用的枚举。你可以在下面看到一个例子: 该类在实体层中用作嵌入变量,如下所示: 系统工作得很好,但是发生了一些错误,ShapeEnum的字段。RE

  • 问题内容: 我找到了一个代码,它声明了如下代码 它们之间有什么区别或相同?还是与或不同? 问题答案: 完全没有区别。根据 Java语言规范的8.3.1-类-字段修饰符, 如果两个或多个(不同的)字段修饰符出现在字段声明中,则按惯例(尽管不是必需的),它们的出现顺序与上面FieldModifier生产中所示的顺序一致。 对于字段,所述生产按以下顺序列出修饰符: 对于方法:

  • 问题内容: 给定下面的Java代码,您可以在Ruby类中最接近地表示这两个变量吗?而且,在Ruby中是否可以像Java中那样区分和变量? 问题答案: Ruby中确实没有等效的构造。 但是,您似乎犯了一个经典的移植错误:您有一种使用语言A 的 解决方案 ,然后尝试将其翻译为语言B,这时您真正应该做的就是找出 问题 ,然后找出解决方法使用语言B。 我不能完全确定您要从那个小的代码段中解决什么问题,但是