String对象是不可变的,每个看起来会修改String对象的方法,实际上都是创建了一个新建的String对象,最初的String对象则丝毫未动。
public class Immutable { public static String upcase(String s) { return s.toUpperCase(); } public static void main(String[] args) { String q = "howdy"; String qq = upcase(q); System.out.println(qq); System.out.println(q); } }
Output:
howdy
HOWDY
howdy
当把q传递给upcase方法时,实际上传递的是q引用的一个拷贝。
下面通过一些例子来说明String类型的一些特性,以及不同方式创建String对象的区别:
例1:
private static void test1() { String a = "a" + "b" + 1; String b = "ab1"; System.out.println(a == b); }
这个例子返回true;很多人会感到很奇怪,==操作符不是判断两个引用(如果对象不是基本数据类型)是否相等的嘛,怎么会是true呢?
那是因为,当编译器在编译代码:String a = "a" + "b" + 1;时,会将其编译为:String a = "ab1";。为何?因为都是“常量”,编译器认为这3个常量叠加会得到固定的值,无须运行时再进行计算,所以就会这样优化。
例如,就拿上面叠加字符串的例子来说,如果几个字符串叠加中出现了“变量”,即在编译时,还不确定具体的值是多少,那么JVM 是不会去做这样的编译时合并的。
例2:
private static String getA() { return "a"; } public static void test2() { String a = "a"; final String c = "a"; String b = a + "b"; String d = c + "b"; String e = getA() + "b"; String compare = "ab"; System.out.println(b == compare); System.out.println(d == compare); System.out.println(e == compare); }
结果:
false
true
false
第 1 个输出false。
“b”与“compare”对比,compare 是一个常量,那么b为什么不是呢?因为b = a +"b",a并不是一个常量,虽然a作为一个局部变量,它也指向一个常量,但是其引用上并未“强制约束”是不可以被改变的。虽然知道它在这段代码中是不会改变的,但运行时任何事情都会发生,尤其是在“字节码增强”技术面前,当代码发生切入后,就可能发生改变。所以编译器是不会做这样优化的,所以此时在进行“+”运算时会被编译为下面类似的结果:
StringBuilder temp = new StringBuilder();
temp.append(a).append("b");
String b = temp.toString();
第二个输出true。
因为c被声明为一个final变量,也就是不可变的,所以编译器会进行优化
第三个输出false。
虽然getA()返回一个常量的引用,但是编译器并不会去看方法内部做了什么。另外,即使返回的是一个常量,但是它是对常量的引用实现一份拷贝返回的,这份拷贝并不是final 的。
编译器优化一定是在编译阶段能确定优化后不会影响整体功能,类似于final引用,这个引用只能被赋值一次,但是它无法确定赋值的内容是什么。只有在编译阶段能确定这个final引用赋值的内容,编译器才有可能进行编译时优化(请不要和运行时的操作扯到一起,那样你可能理解不清楚),而在编译阶段能确定的内容只能来自于常量池中,例如int、long、String 等常量,也就是不包含new String()、new Integer()这样的操作,因为这
是运行时决定的,也不包含方法的返回值。因为运行时它可能返回不同的值,带着这个基本思想,对于编译时的优化理解就基本不会出错了。
例3:
public static void test3() { String a = "a"; String b = a + "b"; String c = "ab"; String d = new String(b); String str1 = "java";// 指向字符串池 String str2 = "blog";// 指向字符串池 String s = str1 + str2; System.out.println(b == c); System.out.println(c == d); System.out.println(c == d.intern()); System.out.println(b.intern() == d.intern()); System.out.println(s == "javablog"); }
结果:
false
false
true
true
false
第一个和第二个false上面的例子已经解释过了。
第三个和第四个true。
当调用 intern()方法时,JVM 会在这个常量池中通过equals()方法查找是否存在等值的String,如果存在,则直接返回常量池中这个String对象的地址;若没有找到,则会创建等值的字符串(即等值的char[]数组字符串,但是char[]是新开辟的一
份拷贝空间),然后再返回这个新创建空间的地址。只要是同样的字符串,当调用intern()方法时,都会得到常量池中对应String 的引用,所以两个字符串通过intern()操作后用等号是可以匹配的。
第五个false。
String str1="java";//指向字符串池
String str2="blog";//指向字符串池
String s = str1+str2;
+运算符会在堆中建立起两个String对象,这两个对象的值分别是“java”,"blog",也就是说从字符串常量池中复制这两个值,然后再堆中创建两个对象。然后再建立对象s,然后将“javablog”的堆地址赋给s。这句话共创建了3个String对象。这时s指向的是堆内存中地址。
总之,创建字符串有两种方式:两种内存区域(pool,heap)
1.""创建的字符串在字符串池中。
2.new 创建字符串时,首先查看池中是否有相同的字符串,如果有则拷贝一份放到堆中,然后返回堆中的地址;如果池中没有则在堆中创建一分,然后返回堆中的地址,
3.在对字符串赋值时,如果右操作数含有一个或一个以上的字符串引用时,则在堆中再建立一个字符串对象,返回引用如:String s= str1+"blog";
String内建对正则表达式的支持
matches(String regex);
split(String regex);
split(String regex, int limit); limit限制字符串分割的次数;
replaceFirst(String regex, String replacement);
replaceAll(String regex, String replacement);
参考:
java特种兵