头图
String应该是面试中被考烂的问题,但是不知道为什么还总是有面试官喜欢问。下面就从面试长问的几个问题出发,来深入理解下String。

String、StringBuiler和StringBuffer有什么区别?

这个问题主要想问的是你对这三个类的理解,这里可以不用说原理,诱导面试官继续问下去.
  1. String类是不可变的,而StringBuiler和StringBuffer类是可变的;
  2. 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[];
    ...
}
  1. String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法;
  2. 用于保存值的数组也是被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还有什么可以优化的地方?

成长在于一点一滴的积累,而我再路上。欢迎大家关注和点赞。


哪能一直都快乐
1 声望0 粉丝

java工作者,旅游爱好者,唠嗑痴迷者