1. 这段程序的结果是什么?
private static void test1() {
String a = "a" + "b" + 1;
String b = "ab1";
System.out.println(a == b);
}
运行结果:
true
为什么呢?
a 引用是直接赋值的,b 引用是通过“+”赋值的,a 和b 两个引用为什么会指向同一
个内存单元?这就是JVM 的“编译时优化”
当编译器在编译代码:String a = "a" + "b" + 1;时,会将其编译为:String a = "ab1";。
为何?因为都是“常量”,编译器认为这3 个常量叠加会得到固定的值,无须运行时再
进行计算,所以就会这样优化。
关于“==”
首先要知道“==”用于匹配内存单元上的内容,其实就是一个数字,计算机内部也只
有数字,而在Java 语言中,当“==”匹配的时候,其实就是对比两个内存单元的内容是否一样。
如果是原始类型 byte、boolean、short、char、int、long、float、double,就是直接比较
它们的值。这个大家应该都清楚,这里不再详谈。
如果是引用(Reference),比较的就是引用的值,“引用的值”可以被认为是对象的逻
辑地址。如果两个引用发生“==”操作,就是比较相应的两个对象的地址值是否一样。换
一句话说,如果两个引用所保存的对象是同一个对象,则返回true,否则返回false(如果
引用指向的是null,其实这也是一个JVM 赋予给它的某个指定的值)。
理解寓意:大家各自拿到了一个公司的offer,现在我们看看哪些人拿到的offer 是同一
个公司的。
关于“equals()”
equals()方法,首先是在Object 类中被定义的,它的定义中就是使用“==”方式来匹配
的(这一点大家可以参看Object 类的源码)。也就是说,如果不去重写equals()方法,并且
对应的类其父类列表中都没有重写过equals()方法,那么默认的equals()操作就是对比对象的地址。
equals()方法之所以存在,是希望子类去重写这个方法,实现对比值的功能,类似的,
String 就自己实现了equals()方法。为什么要自己去实现呢?因为两个对象只要根据具体业
务的关键属性值来对比,确定它们是否是“一致的或相似的”,返回true|false 即可。
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”对比,根据代码清单1-2 中的解释,compare 是一个常量,那么b
为什么不是呢?因为b = a + "b",a 并不是一个常量,虽然a 作为一个局部变量,它也指向
一个常量,但是其引用上并未“强制约束”是不可以被改变的。虽然知道它在这段代码中
是不会改变的,但运行时任何事情都会发生,尤其是在“字节码增强”技术面前,当代码
发生切入后,就可能发生改变。所以编译器是不会做这样优化的,所以此时在进行“+”运
算时会被编译为下面类似的结果:
StringBuilder temp = new StringBuilder();
temp.append(a).append("b");
String b = temp.toString();
注:这个编译结果以及编译时合并的优化,并非胖哥凭空捏造的,在后文中探讨javap
命令时,这些内容将得到实际的印证。
第 2 个输出true。
与第 1 个输出false 做了一个鲜明对比,区别在于对叠加的变量c 有一个final 修饰符。
从定义上强制约束了c 是不允许被改变的,由于final 不可变,所以编译器自然认为结果是
不可变的。
final 还有更多的特性用于并发编程中,我们将在第5 章中再次与它亲密接触。
第 3 个输出false。
它的叠加内容来源于一个方法,虽然方法内返回一个常量的引用,但是编译器并不会
去看方法内部做了什么,因为这样的优化会使编译器困惑,编译器可能需要递归才能知道
到底返回什么,而递归的深度是不可预测的,递归过后它也并不确保一定返回某一个指定
的常量。另外,即使返回的是一个常量,但是它是对常量的引用实现一份拷贝返回的,这
份拷贝并不是final 的。
3. 这段程序的结果是什么?
public static void test3() {
String a = "a";
String b = a + "b";
String c = "ab";
String d = new String(b);
println(b == c);
println(c == d);
println(c == d.intern());
println(b.intern() == d.intern());
}
结果:
false
false
true
true
当调用 intern()方法时,JVM 会在这个常量池中通过equals()方
法查找是否存在等值的String,如果存在,则直接返回常量池中这个String 对象的地址;若
没有找到,则会创建等值的字符串(即等值的char[]数组字符串,但是char[]是新开辟的一
份拷贝空间),然后再返回这个新创建空间的地址。只要是同样的字符串,当调用intern()
方法时,都会得到常量池中对应String 的引用,所以两个字符串通过intern()操作后用等号
是可以匹配的。
4. String“+”
原始代码为:
String a = "a";
String b = "b";
String c = a + b + "f";
编译器会将它编译为:
String a = "a";
String b = "b";
StringBuilder temp = new StringBuilder();
temp.append(a).append(b).append("f");
String c = temp.toString();
5. String、StringBuffer与StringBuilder之间区别
关于这三个类在字符串处理中的位置不言而喻,那么他们到底有什么优缺点,到底什么时候该用谁呢?下面我们从以下几点说明一下
5.1.三者在执行速度方面的比较:StringBuilder > StringBuffer > String
5.2.String <(StringBuffer,StringBuilder)的原因
String:字符串常量
StringBuffer:字符创变量
StringBuilder:字符创变量
从上面的名字可以看到,String是“字符创常量”,也就是不可改变的对象。对于这句话的理解你可能会产生这样一个疑问 ,比如这段代码:
String s = "abcd";
s = s+1;
System.out.print(s);// result : abcd1
我们明明就是改变了String型的变量s的,为什么说是没有改变呢? 其实这是一种欺骗,JVM是这样解析这段代码的:首先创建对象s,赋予一个abcd,然后再创建一个新的对象s用来 执行第二行代码,也就是说我们之前对象s并没有变化,所以我们说String类型是不可改变的对象了,由于这种机制,每当用String操作字符串时,实际上是在不断的创建新的对象,而原来的对象就会变为垃圾被GC回收掉,可想而知这样执行效率会有多底。
而StringBuffer与StringBuilder就不一样了,他们是字符串变量,是可改变的对象,每当我们用它们对字符串做操作时,实际上是在一个对象上操作的,这样就不会像String一样创建一些而外的对象进行操作了,当然速度就快了。
5.3.一个特殊的例子:
String str = “This is only a” + “ simple” + “ test”;
StringBuffer builder = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);
你会很惊讶的发现,生成str对象的速度简直太快了,而这个时候StringBuffer居然速度上根本一点都不占优势。其实这是JVM的一个把戏,实际上:
String str = “This is only a” + “ simple” + “test”;
其实就是:
String str = “This is only a simple test”;
所以不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的String对象的话,速度就没那么快了,譬如:
String str2 = “This is only a”;
String str3 = “ simple”;
String str4 = “ test”;
String str1 = str2 +str3 + str4;
这时候JVM会规规矩矩的按照原来的方式去做。
5.4.StringBuilder与 StringBuffer
StringBuilder:线程非安全的
StringBuffer:线程安全的
当我们在字符串缓冲去被多个线程使用是,JVM不能保证StringBuilder的操作是安全的,虽然他的速度最快,但是可以保证StringBuffer是可以正确操作的。当然大多数情况下就是我们是在单线程下进行的操作,所以大多数情况下是建议用StringBuilder而不用StringBuffer的,就是速度的原因。
对于三者使用的总结: 1.如果要操作少量的数据用 = String
2.单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
3.多线程操作字符串缓冲区 下操作大量数据 = StringBuffer