String经常在一个语言中或多或少都有些特殊地位。在Java亦不例外。今天先来讨论,String是不可变的。
String是引用类型,String变量储存一个地址,地址指向内存堆中的String对象。当我们说变量不可变,有两种不可变性:
- 变量储存的地址不可变;
- 地址指向的对象内容不可变。
String的不可变指的是哪一种?下面用例子来看。
通常有人在疑问String不可变时,会举这样的例子:我们平时不都像下面这样在“修改”String字符串吗:
String s = "hello,world";
s = "Hello,coder";
System.out.println(s); //Hello,coder
我认为这只是一个语义上的误导。赋值操作符=
通常作用于基本数据类型时,确是修改变量的值。所以在这里让人误以为也是修改了变量的内容。但是对于引用类型String,s="Hello,coder"
的实际作用是将变量s
指向另一个内容为Hello,coder
的新的对象。
所以,对于String不可变性的结论显而易见了:String变量指向的地址是可变的,他的不可变性当然说的是第二种——地址指向的对象内容不可变。
纵览String的方法,String类确实没有提供能从String外部修改对象的方法。我们熟悉的replace
,substring
等等方法都要返回一个String,其实都是在返回一个新的对象,而没有修改原有的对象。
为什么要设计成对象内容不可变?
String常量池
一个说法是便于实现String常量池。String存在于常量池中,当新创建一个字符串变量,如果字符串在内存中已经存在,那么就会把这个已经存在于常量池对象的地址赋给变量。这样可节省内存开销。
但这不完全对。
只有两种情况下创建的String变量,他们才会指向常量池中的同一对象:
- String是字面直接量赋值创建。字面直接量是编译期常量,变量会指向常量池中的对象。所有字面量赋值创建的String变量都会指向常量池中的对象。
- 通过String类的intern()方法得到String变量也是指向常量池中的对象。
但是通过构造函数new String()
的并不是。通过构造函数创建的String变量在机制上与其他对象一致,都是在heap上创建新的对象,然后把引用赋给变量。
简单例子验证。
String a = "hello,world";
String b = "hello,world";
boolean c = (a == b); //true
String d = new String("hello,world");
String e = new String("hello,world");;
boolean f = (d == e); //false
String g = d.intern();
boolean h = (a == g); //true
此外,String的方法substring
,relpace
和split
等方法实现均是调用了构造函数创建了新的对象,所以他们返回的String也都是存在于heap上的新对象。
综上来看似乎节省不了多少内存,毕竟程序中编译常量是少数,大多数String都是在运行时产生。
线程安全
这一点显而易见,对象内容都不可变了,自然不会有线程安全问题。
代码安全
String经常被用来存储server connection, database connection或者file path等等。如果String可变,一旦代码某处改动了字符串,会对系统有安全和稳定性威胁。发生在其他普通字符串,则是不可预料的bug。尤其是代码复杂度很高的时候,一个字符串对象被多个变量引用,直接修改对象内容,引起所有引用该对象的变量都发生变化,容易引起bug。
当然这一点主要是降低程序员的错误和bug的可能。
其实,话说回来,在JAVA里,String不可变也并非特殊,所有包装类Interger,Boolean等都是不可变类。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。