String深入理解

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特种兵

猜你喜欢

转载自cc414011733.iteye.com/blog/2288636