Java的类库中有一个很特殊的类,就是String。我认为它主要特殊在两点。
第一点,它重载了“+”和“+=”操作符。Java不允许程序员重载任何操作符。Java对String的这种设计初衷,是为了程序员能够更方便的使用String这个类,但是却带来了意想不到的结果:重载带来了内存开销。《Java编程思想》书中第十三章也表达了这个意思。
Java重载的实际实现方式,借助了StringBuilder这个类,使用“+”或“+=”时,编译器自动生成了一个StringBuffer实例,依次append字符串,最后用toString方法返回结果。例如
String a = "first";
String b = "second";
String c = a + b;
第三句在实际执行时,等价于:
StringBuilder builder = new StringBuilder();
builder.append(a);
builder.append(b);
String c = builder.toString();
如果StringBuilder对象只有一个,是没有实际增加内存开销的。但我们常常会在一个循环中使用”+“或”+=“对String进行拼接,每次进入循环体,就会生成一个StringBuilder对象,无疑是增大了开销。这时推荐使用StringBuilder来执行字符串的拼接业务。
Java中还有一个类StringBuffer,它的也能处理String的拼接操作。这二者方法几乎一致,区别在于,StringBuffer是线程安全的,而StringBuilder不是。因此,前者更耗资源一些。
String的第二个特殊点在于,JVM为String类开辟了常量池。需要关注的是intern()这个native方法
/**
* Returns an interned string equal to this string. The VM maintains an internal set of
* unique strings. All string literals found in loaded classes'
* constant pools are automatically interned. Manually-interned strings are only weakly
* referenced, so calling {@code intern} won't lead to unwanted retention.
*
* <p>Interning is typically used because it guarantees that for interned strings
* {@code a} and {@code b}, {@code a.equals(b)} can be simplified to
* {@code a == b}. (This is not true of non-interned strings.)
*
* <p>Many applications find it simpler and more convenient to use an explicit
* {@link java.util.HashMap} to implement their own pools.
*/
public native String intern();
所有已经被加载过的类的字面值(即双引号引起来的字符串),都被存储到常量池里去了。常量池里的String是unique的,也就是存储时会先检查,如果已经有了这个字符串,将不会添加而是直接返回已有常量的引用。也就是说
String str = new String(“abc”);
new 代表在堆里分配了空间,里边放了"abc"这个String对象。需要说明的是"abc"这个字面值去哪儿了?JVM会检查它"maintains an internal set ofunique strings",也就是看看这个常量池中是否已有"abc",如果没有会把"abc"添加到常量池中去!这种情况下,这行代码实际上创建了两个对象,一个在堆里,一个在常量池里。
intern方法是public的,它的作用是将String对象的字串存入常量池。
有意思的是String对象是不可被修改的,每一个构造的String对象,都是作为常量存储在内存中。引用可以指向别的位置,但对象本身没有被改变,对象本身一直作为常量存在,直到被回收。
还拿上边的三句来说明
String a = "first";
实际上是先为"first"这个常量开辟一块空间,然后声明实例化String的对象a。实例化a的过程是,JVM先扫描是否存在"first"这个String常量,如果有,将a指向它。此时"first" == a 值为true。如果我们定义
String a1 = "first";
那么 a1 == a也是为true的。对a或a1的任何操作所带来的字符串的改变都将产生新的String对象,并将a或a1重新指向这个新生成的对象。比如
a += "A";
此时,执行的步骤是:
1、生成”A"String对象;
2、拼接"first"和"A",步骤见上;
3、返回的"firstA"称为新的String对象,将a指向"firstA"。这时 a != a1。
关于这一段的理解,可以对比:
Integer i = new Integer(2);
Integer j = new Integer(2);
System.err.println(i == 2);//true
System.err.println(j == 2);//true
System.err.println(j == i);//false
使用String时的注意事项:
1、subString生成的对象会保留对原String对象的引用,如果原String对象比较大且又不再被需要,使用new String(src.subString(params...))。
2、""和null是不一样的,就像空数组和NULL不一样,是一个道理。
3、要学会使用正则表达式。