String应该是面试中被考烂的问题,但是不知道为什么还总是有面试官喜欢问。下面就从面试长问的几个问题出发,来深入理解下String。
String、StringBuiler和StringBuffer有什么区别?
这个问题主要想问的是你对这三个类的理解,这里可以不用说原理,诱导面试官继续问下去.
- String类是不可变的,而StringBuiler和StringBuffer类是可变的;
- StringBuiler是线程不安全的,StringBuffer是线程安全的。
到这里,你已经给自己挖了一些坑了,填了这些坑不出意外面试的节奏也已经掌握到你的手里了。
为什么说String类是不可变的?
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
...
}
- String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法;
- 用于保存值的数组也是被private final修饰的,说明它也是不可变的。
由于这两个原因,一旦String被创建那就肯定是不可变的。
那为什么下面的字符串s还是改变了?
public static void main(String[] args) {
String s = "abc";
s += "12345";
System.out.println(s); // abc12345
}
对上面的代码使用javap命令进行反编译:
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String abc
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: aload_1
11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14: ldc #6 // String 12345
16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: astore_1
23: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_1
27: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: return
对于+
这种方式的拼接的底层其实是使用了StringBuiler,先初始化一个StringBuilder对象,然后使用append()方法拼接,最后使用toString()方法得到结果。相当于如下代码:
public static void main(String[] args) {
String s = "abc";
StringBuilder sb = new StringBuilder();
sb.append(s);
sb.append("12345");
s = sb.toString();
System.out.println(s);
}
查看StringBuilder.toString()
方法,你会发现重新创建了一个String对象,所以其实是将新的引用指向了s,而不是s本身发生了改变。
那么将String类设计成不可变的意义是什么?
安全性:String被许多Java类用作参数,比如URI、反射、文件等,这就需要字符串不可变来保证多线程安全,而不用再写复杂的代码来保证。
JVM如何优化字符串频繁创建的问题?
字符串对象需要消耗大量资源,因此JVM为了提高性能和减少内存的开销,提出了使用字符串常量池进行优化。在创建String对象时,判断字符串是否在字符串常量池中,如果在,则直接返回常量池中的引用。
由此,可以看出下面这个结果是true:
public static void main(String[] args) {
String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2); // true
}
扩展,下面的代码共创建几个实例?
String s1 = new String("abc");
String s2 = "abc";
String s3 = new String("abc");
答案是3个,可以通过jvisualvm查看
通过new String()的方式会创建1个或2个,而使用字符串创建的方式则可能是0个。第一行创建两个对象,堆中创建了一个String对象,字符串常量池创建了一个。第二行发现常量池中已经有"abc"这个字符串对象实例,直接返回。第三行,在堆中又创建一个String对象。
StringBuffer如何保证线程安全?
在StringBuffer
的方法上都被加上了synchronized
关键字。关于synchronized
会再说到。
总结
String是日常开发中最长用到的类,设计者也更加慎重。所以学习这些基础的类,也能有所收获,这也是面试官乐此不疲的原因吧。关于String其实还有很多内容可以说,比如空指针异常,在日常项目中很多空指针异常都来源于字符串操作上,所以在有些开发规范中也强制要求使用StringUtils工具类来操作。
提问
你觉得String还有什么可以优化的地方?
成长在于一点一滴的积累,而我再路上。欢迎大家关注和点赞。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。