0. 前言
如果一个对象,在它创建完成之后不能再改变它的状态,包括对象内的成员变量、基本数据类型的值等等。那么这个对象就是不可变的。众所周知String类就是不可变的。转载请注明出处为SEU_Calvin的博客。
1. String类为什么是不可变的
首先看一下String类的源码中:
//JDK1.6 public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /**用于存储字符的数组 */ private final char value[]; /** 表示String在这个value数组中的起始位置 */ private final int offset; /** 字符个数*/ private final int count; /** 哈希值 */ private int hash; // Default to 0 //... } //JDK1.7 public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /**用于存储字符的数组 */ private final char value[]; /** 哈希值 */ private int hash; // Default to 0 //.... }
无论是JDK6还是7,String类都是对字符数组的封装(而且value也只是一个引用,它指向一个真正的数组对象)。源码中并没有提供value的set方法,因此String类一旦初始化,外部便无法修改,同时value被修饰为 private final,在String类内部也无法改变。所以String对象是不可变的。
我们常用的字符串方法比如substring、replace、replaceAll、toLowerCase等方法,给人的感觉好像是可以改变String内的值,但是这些方法的内部其实在完成逻辑后创建了一个新的String对象并返回。以replace为例,以下是一个简单的例子证明这一点。
String s = "DF2lian"; s = s.replace('F', 'A'); System.out.println("s = " + s); //输出DA2lian String s = "DF2lian"; s.replace('F', 'A'); System.out.println("s = " + s); //输出DF2lian
2. String类真的不可变吗
从上文可知String的成员变量value是private final修饰的,初始化后不可再改变并且不能再指向其他数组对象。但是value本身是一个引用变量,而不是真正的对象。那么我们就可以通过反射得到String对象中的value属性,进而改变value引用的数组的结构。下面是实例代码:
String s = "DF2lian"; //获取String类中的value字段 Field valueFieldOfString = String.class.getDeclaredField("value"); //改变value属性的访问权限 valueFieldOfString.setAccessible(true); //获取s对象上的value属性的值 char[] value = (char[]) valueFieldOfString.get(s); //改变value所引用的数组中某个字符 value[1] = 'R'; System.out.println("s = " + s); //输出DR2lian