背景
在Java 101中,我们学到:
String
是不可变的。
是的。很好。谢谢。
然后我们进入Java 102(或者可能是Java 201),我们发现:
使用反射可以更改
String
,所以它并不是真正的不可变。
啊。好吧。这取决于您的观点,它可能非常可爱或无法衡量。
迄今为止,这些事情已经在Stack Overflow和其他地方被广泛讨论。在提出这个问题时,我认为这一点是公认的。
问题
当我们发现
String
并不是真正不可变时,对于我们如何编写代码有什么影响?
让我从两个方面具体化这个问题。
JVM沙箱
是否可能有恶意类利用此技巧来破坏JVM的安全性,并进行不应该的操作?例如,JDK中是否有方法返回 String
,只有在假定它不可更改时才是安全的?
这里有一个有希望的开端:
String prop = "java.version";
// retrieve a System property as a String
String s = System.getProperty(prop);
System.out.println(s);
// now mess with it
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] value = (char[])field.get(s);
value[0] = 'x';
//turns out we've changed not just the String we were
//given but the underlying property too!
System.out.println(System.getProperty(prop));
这段代码从JVM检索系统属性,以
String
返回,然后更改String
;这样做的结果是基础属性也被更改了。这种方法会引起混乱吗?我不确定。值得注意的是,您必须具有执行反射的权限。在这个点上,安全性是否已经受到威胁?它让我能够避免没有权限更改安全属性,但这是否可以预料,还是个问题?
有什么办法可以扩展这个方法来做一些更糟糕的事情吗?
防御性克隆
我们总是被告知,在传递给可能修改它们的方法之前,最好克隆数组和其他对象,除非我们希望它们被修改。这只是良好的编程实践。如果你把数组的唯一副本交给别人,然后它被弄乱了,那就是你自己的错误。
但看起来同样的论点也应该适用于一个
String
!我从未听说过任何人说我们应该在将String
传递给其他人编写的方法之前进行克隆。但是,如果一个String
与一个数组一样可变,这有什么不同呢?支持防御性克隆的论点
如果防御性克隆只是为了不真正知道方法可能对我们传递的事物做什么,而且如果我们正在传递它是可变的,那么我们应该进行克隆。如果代码可能是恶意的,则可能会更改
String
;因此,每当重要的是String
保持不变时,我们都应该确保发送副本而不是真实的东西。不能说不信任的代码应该在安全管理器下运行,如果我们不相信它不会对
String
做坏事。如果对于一个String
来说是这样的话,那么对于一个数组也是一样的;但没有人说只有在将代码与安全管理器锁定的情况下才需要克隆数组和其他对象。反对防御性克隆的论点
一个数组可能会被粗心的编码修改;换句话说,它可能会被无意地修改。但没有人会无意中更改
String
:只能通过狡猾的技巧来完成,这意味着程序员试图打破不可变性。那就区分了两种情况。我们绝对不能把一个数组(即使是克隆后的)传递给我们完全不信任的代码。你不可能从某个不靠谱的地方下载一个库,然后认为自己已经安全,因为你克隆了自己的数组。你之所以要守卫克隆操作是因为你认为程序员可能会犯错误,或者对于你发送的数据所允许的内容有不同的假设。如果你担心代码可能会背着你修改 "String",那么你根本不应该运行这段代码。你只是增加了性能开销,通过克隆 "String" 所达到的目的根本毫无意义。
答案是什么?
我们应该如何思考这个问题?这些技巧可以打破JVM沙盒吗?我们是否应该在考虑到 String
的可变性时进行防御性编码,还是这只是一个转移注意力的话题?