为什么说String
类型不可变?
-
String
的源码里其实使用一个char
数组来存储字符串的,String
之所以说不可变,就是因为这个char数组它是private
类型,而且String
没有对外暴露和提供修改这个char
数组的方法,因此我们无法更改这个char
数组的值,所以String
是不可变的。 -
-
注意:这里
char
数组被final
修饰不代表不可变,因为数组是引用类型,final修饰引用类型的时候代表引用不可更改,即我们只是不能将这个value
属性指向新的引用,但是它原本指向的这个char
数组中的字符我们是可以更改的。 -
因此,如果我们可以通过更改
value
指向的char
数组本身,就可以改变字符串。正常情况下我们是无法更改的,这也是我们说字符串不可变的原因,但是我们通过极端方法也是可以更改的,例如使用反射可以得到这个私有的value属性,然后通过爆破我们就可以访问这个私有属性了,例如:String s = "aabbc"; Class<? extends String> aClass = s.getClass(); Field value = aClass.getDeclaredField("value");//得到这个value属性 value.setAccessible(true);//将它设置成可以访问 char[] chars = (char[]) value.get(s); System.out.println(s);//在修改之前输出s chars[0] = 'k'; //修改s System.out.println(s);//输出修改之后的s
结果如下:
- 可以看出这个字符串确实被改变了。
证明我们是真的修改了字符串
字符串的hashCode()
方法
-
这里我们不能用
hashCode()
返回的哈希值来判断是否是同一个字符串,因为hashCode
码值不等于地址,字符串的hashCode
码是通过对每个字符进行一定的规则的累加得到的,然而String
类中有一个int类型的属性叫hash
,当这个hash
=0的时候就会去计算这个String的hashCode
码值,但是计算完后就会将计算结果赋值给这个hash
属性;如果hash
不等于0就返回这个属性值作为当前字符串的hashCode
。因此我们第一次计算调用hashCode()
的时候由于int
类型的属性默认为0,所以会逐个字符计算hash
值,第二次调用hashCode()
的时候返回的hash
值其实是第一次计算出来缓存好的,因此肯定是相同的,所以此时调用hashCode()
方法返回的值无法用来判断是否为同一个字符串。-
源码如下:
private int hash; // Default to 0 public int hashCode() { int h = hash;//得到当前属性hash的值 if (h == 0 && value.length > 0) {//如果它为0就计算当前字符串的hashCode char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h;//计算完了后赋值给hash属性 } return h; }
-
-
我们可以这样判断修改过后的字符串到底是原地修改得到的还是重新赋值得到的:将这个字符串的引用赋值给另一个字符串变量tempC,我们修改这个字符串前后都输出这个tempC,如果我们是原地修改,那么这个tempC输出的值前后两次肯定不同;如果是重新赋值,那么tempC的值前后两次肯定一样,因为我们如果是重新赋值的话只是更改了s的引用,并没有更改tempC的引用。
-
String s = "aabbc"; Class<? extends String> aClass = s.getClass(); Field value = aClass.getDeclaredField("value");//得到这个value属性 value.setAccessible(true);//将它设置成可以访问 char[] chars = (char[]) value.get(s); String tempC = s; System.out.println(tempC );//在修改之前输出tempC chars[0] = 'k'; //修改s System.out.println(tempC );//输出修改之后的tempC
- 结果如下:
- 结果如下:
-
如果是重新赋值的情况,例如:
String s = "aabbc"; String tempC = s; System.out.println(tempC); s = "sss"; System.out.println(tempC);
- 结果如下:
- 结果如下:
-
-
因此可知字符串确实是可以通过反射修改的。