String为什么是不可变的?

  • Post author:
  • Post category:其他


我们都知道String一旦定义后长度和内容是不可变的,那么我们要想知道String为什么是不可变的,需要从以下几个角度来看:

1.从源代码角度来看

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

在定义字符串时,会将字符串内容保存到一个使用 private final修饰的char[  ]之中,从源码中我们可以得知该数组被final修饰,因此它的引用地址不能改变。

但是这并不代表char value[  ]数组中的内容不可变,我们依旧可以通过数组下标来修改value数组,从此看来在源代码中String不可变的主要原因是存放数据的char[ ]数组被private修饰,我们从外部无法去访问到char[  ],并且String内部本身也没有向我们提供修改char value[ ]的API, 从而无法对字符串内容进行修改。

public class Test {
	private final static char[] value = {'a','b','c'};
	public static void main(String[] args) {
		value[0] = 'A';//尽管value数组被final修饰,但我们依然可以通过数组下标对value数组进行内容的修改

	}
}

2、从缓存池角度来看


String缓存池

String Pool 是在方法区的一块特殊存储区域。当一个String被创建时如果发现当前String已经存在于String Pool,则会返回一个已存在String的引用而不会新建一个对象。

以下代码只会创建一个String对象在堆内存中。

String s1 = "hello";
String s2 = "hello";

在缓存池中,如果一个String是可变的,改变了一个引用指向的String,那么就会导致其他引用得到错误的值。因此String不可被改变。


3.从使用频率来看

在Java中,对于String的Hashcode使用是非常频繁的,例如在HashMap或HashSet中。将String设计成不可变可以保证他的Hashcode始终一致,这样Hashcode就可以被缓存并且不用担心变化。这就意味着,不需要在每次使用String的时候都去计算他的Hashcode,这也使得程序运行的更加高效。

4.从安全性能来看

String在很多Java类中被广泛用作参数,但是我们知道不可变对象是天生线程安全的,因为不可变对象不能被改变,他们可以在多线程中被自由的共享,这就消除了对象同步的需求。那么从这个层面来说,String被设计成不可变的出发点是效率和安全。这也是不可变类在很多情况下被优先使用的原因。


那么String对象真的不可变吗?

上面我们提过char value[  ]数组是使用private final修饰的,但是这并不代表char value[  ]数组中的内容不可变,我们依旧可以通过数组下标来修改value数组,这只是从String内部进行修改,那么我们如何在外部去修改value[ ]的值呢?

我们可以通过反射来进行修改, 可以反射出String对象中的value属性, 进而改变通过获得的value引用改变字符串的内容。下面是实例代码:

public static void updateReflection() throws Exception {
		//创建字符串
	    String s = "Hello World";
	     
	    System.out.println("s = " + s);
	     
	    //获取String类中的value字段
	    Field valueFieldOfString = String.class.getDeclaredField("value");
	     
	    //改变value属性的访问权限
	    valueFieldOfString.setAccessible(true);
	     
	    //获取s对象上的value属性的值
	    char[] value = (char[]) valueFieldOfString.get(s);
	     
	    //改变value所引用的数组中的第5个字符
	    value[5] = '.';
	     
	    System.out.println("s = " + s);  
	}

在此过程中,我们通过反射的技术获取了String类中的value数组,通过数组下标修改了其内容,但字符串s的引用一直未发生改变。



版权声明:本文为qq_51956240原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。