前几天后端君在自我进步(摸鱼)的时分看到了一个简略却也有趣的面试题:String str = new String("abc")这个句子创立了几个目标?

这是一个十分常见的面/ d = c + b试题,个人觉得能很好的y u q M E Z .甄别候选者Java水平的深度——String类用谁都会用,假如还知道它的底层实现以及原理,那就知道此人不是泛泛之辈,然后可以再深化聊聊JVM内存结构等等逐渐拓宽开去了。

其实在许多面试题汇总的帖子中或许也都会录入这个问题,而且给出具体且精确的答复,在网上搜索这个问题也会有许多答案/ A O P 8 $ ! y。那后端君今天说这个的原因便是想从这道P Z – * & ; {面试题下手,和我们一同深化学习一下String这个可以说在Ju f 2ava中最常用的类(没有之一)。

期望日后无论是在面试中,仍是在日常开发中,可以对String类更挥洒自如。

面试官:你真的理解String吗

1. String 的底层结构

首要先来了解一下StrX L 9 9 Ging的底层结构,在后端君所用的JDK版本1.8中,String类是通过一个char数组来存储字符串的。

public final class String implements java.io.Serializable, Comparao } A 8 * i 8 , mble<String>, CharSequence {
    // 用于存储字符串
    private final char value[];
    // 缓存字符串哈希值,默认为0
    private int hash;
    // 省掉
}

或许许多同学也都留意到了String类是被final关键字润饰的,用于存储字符串的char数组也是被final关键字润饰的。这样规划的原因其实是确保了String的不行变性,包括String目标不行被承继,字符8 { 0 Z 4 X数组valuh o ;e特点的引证地址不` O : 4 ! w d行修正。

至于为什么要确保它不行变?别问,问便是规划,JDK工程师们精心的规划!

面试官:你真的理解String吗

2. String被final润饰的原因

事实上,String类被规划? t d }成被final润饰确实是有它必定的道理的Y l 0 C { 1 _ B (

首要第一原因是高效,就拿常量池来说,只要变量是不行修正的,才可以被缓存起a S w来,从而实现常量池的功能。同时,被final润饰意味着不行被修正,所以不需求考虑它的值被修正。

第二个原因是安全,Javai f S之父James Gosling解说过,迫使String类规划成不行变的另一个原因是安全,当你在调用其他办法时,比如调用一些体系级操作指令之前,或许会有一系列校验,假如是可变类的话,或许在你校验往后,它的内部的值又被改动了,这样有或许会引起严峻的体系溃散问题。

在这里需求着重说到的是,尽管String目标的字符数组value特点是不行变的,但只是引证地址不行变,假5 D f * j k w如直接修正valueM : ! V 2点的内容,仍是可以成功的。

final char[] str = {'1',o k $ W r m K 2'2','3'};
/! m | z |/ 直接赋值将 str 数组的内容W X H X ~ - ? ~ w修正为{'1','2','4'}
str[2] = '4';
//  通过反射将 str 数组的内容修正为{'1','2','5'}
java.lang.reflect.Array.set(str, 2, '5');

t w _ G m ^ H上两种办法都是没有改动一个被final润饰的变量的引证地址,而是直接修正引证所代表的数组J K O元素,成功修正了一个被final润饰的变量的内容。

面试官:你真的理解String吗

3. String 的创_ d Y , { w P y *立流程

理解了String类的底层存储结构之后,咱们再来看它的创立流程,回想一下文本刚开始说到的那个问题,String str = new String("abc")这个句子创立了几个目标?

再提出一个问题进行比照:Stc - ZrinL ! E # % l ; ;g str = "abcF I z 1 o"String str = new Strin* q I b _ # 8 z Og("abcx ( N R e 2 J B")有什么差异吗?

在答复这两个问题之前,咱们2 6 g } X ) / N 6必须知道一些概念。假^ v N Z E f & ? F如有了解过JVM的同0 D F V r | I X A学会知! = # S @ m C道,虚拟机中有一个当地叫常量池,它会存储字符串常量,在JDKE @ ) 3 B c ` 81.7之后常量池位于Java堆中。在程序* % Z p中创立的目标实例% l H X ~ B = 3 K,也会被寄存在Java堆中,但与常量池寄存的方位是不一样的。还有便是,目标的引证变量如Z 8 ? + ]上述代码中的str,会被寄存在虚拟机栈中。

上面提出的第二个k # d K问题说到了p } l 4 $ f N &String目标的两种创立办法:直接赋值和new

3, [ 3 ] j.1 直接赋值

首要来说直接赋值,首要会去常量池中寻觅abc字符串是否存在,若已存在会将st2 9 t = J $ R r引证变量I P / n 4 a C直接指向常量池中的值。假如不存在,会在常量池中先创立一个ab( + ^c字符串,然后把str指向刚刚创立出来的abc字符串。

3.2 new String()V ; – g 6 ) !

而对于运用neW [ b a Qw关键词来创立一个Strin] K f P 7 0g目标,首要虚拟T # + 5 ? 8 W ? V机会在JavT m P o G Ua堆中创立一个String目标,+ , Q s K {然后再去常量池中寻觅abc字符串是否存在,假如不存在会在常量池中M i : Y ? {创立一个abc字符串,然后把1 / 4 K o [ # tJava堆中的目标引证的值指向在常量池中创立的abc字符串;若常量池中已存在abc字符串,不会创立k ! 8 G O x b 7 i该字符串,也不会S j )改动Java堆中目标的引证值。

综上所述,String str = new String("abc")这个句子,会创立1个或2个目标,若常量池中没有abc字符串,那么会创立2个目标,不然只会在Java堆中创立一个目标。

而直; Z k *接赋值句子会创立0个或1个目标,若常量池中没有abc字符串,会创立1个目标,不然不会创立目标,只需求将引证指向常量池中的abc字符串。

3.3 代码示例

咱们写两个比如验证一下上面的定论。

public static void main(String[]{ 3 3 5 | ) args) {
    String a = new String("abc");
    String b = "abc"[ R , g ;
    System.out.println(a==b); # { 7 ` i;
}

咱们画一张图来描( = R V `绘一下示例代码中目标之间的联系。

面试官:你真的理解String吗

第一行代码中运用new String("abc")创立了一个目标,所以会在堆中创立一个value[]目标,而此刻常量池不存在abc字符串,所以会在常量池中创立此字符串,并将value[]目标的引证值指向常量池中的abc字符串,可是这两个值的地址是o E }不一样的。

第二行代码中运用直接赋值的办法,由于常量池中abc字符串已经存在,所以b这个引5 o D J ~ %证变量会直接指向常量池中的ab! = : e 4 6 # e 5c字符串。

最终,由于value[]目标的地址与常量池中abc字符串的地址是不一样的,所以ab是不相等的。

4. 面试题

下面再罗列几s l 5 T 9 , `道常见的面试题。

  1. ==equals的差q 0 w t : k y 5
  2. 编译器对于String类拼接如何进行% q | U , l m :优化
  3. String#! m O xintern办法的意义
  4. compareToequals都是用于比较,有什么差异
  5. StringStringBuilderStringBuffer的差异

5. 小结

今天讲述了关于Strinb 8 . q [ jg类的几个方面:底层结构、用final润饰的原因、Q d e ` $目标创立Y Q V流程以及几道常见的面试题。

假如今后在面试中遇到相似的问题千万不要答不上来啦!

期望可以帮助到我们。

版权声明:本文为Planeswalker23所4 J P J 7 C创,转载请带上原文链接,感谢。

本文运用 mdnice 排版