具体来说,我正在尝试为需要使用File.separatorChar
在Windows和unix上构建路径的方法创建单元测试。代码必须在两个平台上运行,但当我尝试更改此静态最终字段时,我会收到JUnit错误。
任何人都知道发生了什么事?
Field field = java.io.File.class.getDeclaredField( "separatorChar" );
field.setAccessible(true);
field.setChar(java.io.File.class,'/');
当我这样做时,我得到了
IllegalAccessException: Can not set static final char field java.io.File.separatorChar to java.lang.Character
思想?
答案 0 :(得分:64)
来自Field.set
的文档:
如果基础字段是最终字段,则该方法会抛出
IllegalAccessException
,除非此字段成功setAccessible(true)
且此字段为非静态。
所以起初看起来你运气不好,因为File.separatorChar
是static
。令人惊讶的是, 是一种解决此问题的方法:只需通过反射使static
字段不再final
。
我改编了这个解决方案from 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);
}
我测试了它并且它有效:
setFinalStatic(File.class.getField("separatorChar"), '#');
System.out.println(File.separatorChar); // prints "#"
请谨慎使用此技巧。抛开毁灭性的后果,以下实际上有效:
setFinalStatic(Boolean.class.getField("FALSE"), true);
System.out.format("Everything is %s", false); // "Everything is true"
重要更新:上述解决方案在所有情况下均无效。如果在重置之前可以访问该字段并通过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
答案 1 :(得分:2)
尝试调用不在类File
实例上的文件实例E.g。
File file = ...;
field.setChar(file,'/');
您也可以尝试http://code.google.com/p/jmockit/并模拟静态方法FileSystem.getFileSystem()。 (不知道你是否可以模拟静态变量,通常那些黑客不应该是必要的 - >写oo代码并使用'only'mockito)
答案 2 :(得分:2)
构建文件时只需使用/到处。我已经这样做了13年,从来没有遇到过问题。没有什么可以测试的。
答案 3 :(得分:1)
我意识到这并没有直接回答你的问题,但是Apache Commons FileNameUtils会做跨平台的文件名构建,并且可以省去你自己写的课程。
答案 4 :(得分:0)
不要使用File.separatorChar声明你的服务类,而是让它称之为PathBuilder。这个类将有一个concatPaths()方法,它将连接这两个参数(使用操作系统的分隔符char)。美妙之处在于您正在编写本课程,因此您可以在进行单元测试时随意调整它。
答案 5 :(得分:0)
您可以获取java.io.File的源代码,并修改它以使separatorChar和separator不是final,并添加一个setSeparatorChar方法来更新其中的两个,然后在编译类路径中包含已编译的类。
答案 6 :(得分:0)
这里我要设置" android.os.Build.VERSION.RELEASE"的值,其中VERSION是类名,RELEASE是最终的静态字符串值。
如果基础字段是最终字段,则方法抛出 IllegalAccessException 以便我们需要使用setAccessible(true), 使用 field.set()方法
时,需要添加 NoSuchFieldException
@RunWith(PowerMockRunner.class)
@PrepareForTest({Build.VERSION.class})
public class RuntimePermissionUtilsTest {
@Test
public void hasStoragePermissions() throws IllegalAccessException, NoSuchFieldException {
Field field = Build.VERSION.class.getField("RELEASE");
field.setAccessible(true);
field.set(null,"Marshmallow");
}
}
现在String RELEASE 的值将返回" Marshmallow "。