1. 前言
最近看到几个有趣的关于Java核心类String的问题。
String类是如何实现其不可变的特性的,设计成不可变的好处在哪里。
为什么不推荐使用+号的方式去形成新的字符串,推荐使用StringBuilder或者StringBuffer呢。
2. String类是如何实现不可变的
String类的一大特点,就是使用Final类修饰符。
A class can be declared final if its definition is complete and no subclasses are desired or required.
Because a final class never has any subclasses, the methods of a final class are never overridden .
Java SE 7 官方手册中的定义如上,如果你认为这个类已经定义完全并且不需要任何子类的话,可以将这个类声明为Final,Final类中的方法将永远不会被重写。
在Java中,String是被设计成一个不可变(immutable)类,一旦创建完后,字符串本身是无法通过正常手段被修改的。
选了substring方法来做一个代表,其他常见的涉及String操作的方法都是类似,如果你操作后的内容会和目前String中的内容不一致的话,那么都是重新创建一个新的String类返还,不会让你去修改内部的内容。
将String类设计成Final类,能够避免其方法被子类重写,从而破坏了它本身方法的实现,进而破坏了不可变的特性。
2.1 String类设计成不可变的好处
我们都不是Java语言的设计者,不知道其为何一定要设计成不可变,试着做一些猜想。
1.可以实现多个变量引用JVM内存中的同一个字符串实例。见后文String Pool的介绍。
2.安全性,String类的用途实在太广了,如果可以随意修改的,是不是很恐怖。
3.性能,String大量运用在哈希的处理中,由于String的不可变性,可以只计算一次哈希值,然后缓存在内部,后续直接取就好了。如果String类是可变的话,在进行哈希处理的时候,需要进行大量的哈希值的重新计算。
2.2 String Pool
上文说了,设计成不可变后,可以多个变量引用JVM上同一块地址,可以节省内存空间,相同的字符串不用重复占用Heap区域空间。
String test1 ="abc";
String test2 ="abc";
通常我们平时在使用字符串是,都是通过这种方式使用,那么JVM中的大致存储就是如下图所示。
两个变量同时引用了String Pool中的abc,如果String类是可变的话,也就不能存在String Pool这样的设计了。 在平时我们还会通过new关键字来生成String,那么新创建的String是否也会和上文中的示例一样共享同一个字符串地址呢。
String test1 ="abc";
String test2 ="abc";
String test3 =newString("abc");
答案是不会,使用new关键字会在堆区在创建出一个字符串,所以使用new来创建字符串还是很浪费内存的,内存结构如下图所示。
2.3 不推荐使用+来拼装字符串的原因。
首先我们来看这一段代码,应该是之前写代码比较常见的。
String test1 ="abc";
String test2 ="abc";
String test3 = test1 + test2;
test3通过test1和test2拼接而成,我们看一下这个过程中的字节码。
从以上图我们可以看到,目前的JDK7的做法是,会通过新建StringBuilder的方式来完成这个+号的操作。这是目前的一个底层字节码的实现,那么是不是没有使用StringBuilder或者StringBuffer的必要了呢。还是有的,看下一个例子。
String test2 ="abc";
String test3 ="abc";
for(inti =0; i <5; i++) {
test3 += test2;
}
在上述代码中,我们还是使用+号进行拼接,但这次我们加了一个循环,看一下字节码有什么变化。
每次循环都会创建一个StringBuilder,在末尾再调用toString返还回去,效率很低。继续看下一个例子,我们直接使用StringBuilder,来做拼接。
String test2 ="abc";
// 使用StringBuilder进行拼接
StringBuilder test4 =newStringBuilder("abc");
for(inti =0; i <5; i++) {
test4.append(test2);
}
每次循环体中只会调用之前创建的StringBuilder的append方法进行拼接,效率大大提高。
3. 总结
本文主要探讨了String类设计为Final修饰和不可变类的原因,以及为何在日常工作中不推荐使用+号进行字符串拼接。