使用反射更改静态最终File.separatorChar进行单元测试?

Ste*_*all 26 java reflection file-io unit-testing

具体来说,我正在尝试为一个方法创建单元测试,该方法需要用于File.separatorChar在windows和unix上构建路径.代码必须在两个平台上运行,但当我尝试更改此静态最终字段时,我会收到JUnit错误.

任何人都知道发生了什么事吗?

Field field = java.io.File.class.getDeclaredField( "separatorChar" );
field.setAccessible(true);
field.setChar(java.io.File.class,'/');
Run Code Online (Sandbox Code Playgroud)

当我这样做时,我明白了

IllegalAccessException: Can not set static final char field java.io.File.separatorChar to java.lang.Character
Run Code Online (Sandbox Code Playgroud)

思考?

pol*_*nts 65

从以下文档Field.set:

如果底层字段是final,则该方法抛出一个IllegalAccessException除非setAccessible(true)该字段成功且该字段是非静态的.

所以起初似乎你运气不好,因为File.separatorCharstatic.令人惊讶的一种方法来解决这个问题:简单地让static现场不再final通过反射.

从javaspecialist.eu改编了这个解决方案:

static void setFinalStatic(Field field, Object newValue) throws Exception {
    field.setAccessible(true);

    // remove final modifier from field
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

    field.set(null, newValue);
}
Run Code Online (Sandbox Code Playgroud)

我测试了它,它的工作原理:

setFinalStatic(File.class.getField("separatorChar"), '#');
System.out.println(File.separatorChar); // prints "#"
Run Code Online (Sandbox Code Playgroud)

用这种技术做极其谨慎.抛开毁灭性后果,以下实际上有效:

setFinalStatic(Boolean.class.getField("FALSE"), true);
System.out.format("Everything is %s", false); // "Everything is true"
Run Code Online (Sandbox Code Playgroud)

重要更新:上述解决方案并非在所有情况下都有效.如果该字段在重置之前可以访问并通过Reflection读取,IllegalAccessException则抛出该字段.它失败是因为Reflection API创建了FieldAccessor缓存和重用的内部对象(请参阅java.lang.reflect.Field#acquireFieldAccessor(boolean)实现).示例测试代码失败:

Field f = File.class.getField("separatorChar"); f.setAccessible(true); f.get(null);
// call setFinalStatic as before: throws IllegalAccessException
Run Code Online (Sandbox Code Playgroud)

  • #define FALSE 1回来了,它已经准备好再次踢一些屁股了. (14认同)
  • @NateS:没有人说这是个好主意.我们正在调查是否有可行的事情.显然它是.这不是向我投票的理由.不要射击信使.如果您对我们为什么这样做有疑问,请关注OP的问题.我只是如何展示. (6认同)
  • 哇靠!setAccessible的引入是一个主要的搞砸. (3认同)
  • @NateS`static final`*编译时常量*可以由javac内联.显然情况并非如此.这是尝试测试可能会发生变化的事情.虽然在运行时`final`字段可以内联,但实际上这不应该是单元测试的问题. (3认同)
  • 鼠标悬停在投票上说,"这个答案没用".对不起,但我觉得你的答案符合这个标准.您的回答使得看起来可以更改最终值,而实际上并非如此.内联值不会更改.结果完全不可预测.我很欣赏分享这些深奥的知识,但做你所建议的永远不应该做. (2认同)
  • 似乎不适合我.Field modifiersField = Field.class.getDeclaredField("modifiers"); 抛出异常:java.lang.NoSuchFieldException:修饰符 (2认同)