final String的底层详解

一、发生的现状:

public static void main(String[] args){
		String a="abc";
        final String b="abc";
        String c="abc1";
        System.out.println(a+1==b+1); //false
        System.out.println(a+1==c);	  //false
        System.out.println(b+1==c);   //true
}

上述这个代码块,运行结果两个是false,一个是true!那么为什么会产生这个现象呢?

二、原因说明:

以下内容,我当做各位读者是有相应基础的:第一、“==”知道是比较的什么吧;第二、final 知道是什么吧;第三、分得清字符串池(常量池)、堆空间、栈帧、局部变量表、操作数栈和反汇编(javap指令)。
如果以上说的3点不清楚,那么,请百度查看下相应的资料吧。
首先javap -v 查看反汇编内容:(在这里进行一下简单的注释解释,只复制了main中反汇编内容);
这里面关于final的核心代码是序号: 79;

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=4, args_size=1
      //除了前面说的是操作数栈,后面注释直接用的是栈来代替
         0: ldc           #2 //ldc是将int, float或String型常量值从常量池中推送至操作数栈顶  // String abc
         2: astore_1         //这个其实就是把"abc"从操作数栈弹出,赋值给变量a(局部变量表1的位子变量)
         3: ldc           #2 //这里的#2可以不用管,动态链接的东西   // String abc 
         5: astore_2		 //同理赋值给变量b,这个数字2自行研究去
         6: ldc           #3 // String abc1
         8: astore_3	     //同理赋值给变量c
         9: getstatic     #4 // Field java/lang/System.out:Ljava/io/PrintStream;
        12: new           #5 //创建一个StringBuilder对象,并将其引用值(地址)压入栈顶     // class java/lang/StringBuilder
        15: dup				 //复制栈顶数值并将复制值压入栈顶
        16: invokespecial #6 //根据编译时类型来调用实例方法  // Method java/lang/StringBuilder."<init>":()V
        19: aload_1			 //读取位子1的数据,也就是a变量引用地址的数据推到栈顶
        20: invokevirtual #7 //调用append方法把"abc"添加入StringBuilder中    // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        23: iconst_1         //将int型1推送至栈顶
        24: invokevirtual #8 //append上面那个1                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        27: invokevirtual #9 //调用StringBuilder的toString方法 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        30: ldc           #3//这个点参考下方序号79的解释   // String abc1
        32: if_acmpne     39//比较栈顶两引用型数值,当结果不相等时跳转
        35: iconst_1		//把int类型常量1压入栈
        36: goto          40//跳转到40的序号那行
        39: iconst_0		//把int类型常量0压入栈,实际上这里被goto跳过了,下面是重复的
        40: invokevirtual #10// Method java/io/PrintStream.println:(Z)V
        43: getstatic     #4// Field java/lang/System.out:Ljava/io/PrintStream;
        46: new           #5// class java/lang/StringBuilder
        49: dup
        50: invokespecial #6// Method java/lang/StringBuilder."<init>":()V
        53: aload_1
        54: invokevirtual #7// Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        57: iconst_1
        58: invokevirtual #8 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        61: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        64: aload_3			//直接拿到c变量地址,指向字符串常量池
        65: if_acmpne     72
        68: iconst_1
        69: goto          73
        72: iconst_0
        73: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
        76: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;

//以上代码,对应是java代码的第二个打印结束  System.out.println(a+1==c);
//这里,我们就来看看 如果有final将会有什么区别
        79: ldc           #3//这句是关键,final修饰的b变量,没有了转StringBuilder的动作, 直接是从常量池中拿的abc1,因为c变量的存在,常量池已经存在了abc1;                 // String abc1
        81: aload_3		//装载局部变量3 也就是c变量 abc1
        82: if_acmpne     89 //比较栈顶两引用型数值,当结果不相等时跳转
        85: iconst_1
        86: goto          90
        89: iconst_0
        90: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
        93: return

三、结论说明:

  1. 字符串池最初是空的,由String类私有地维护(可以参考String的intern()方法的解释)。
  2. 字符串底层的计算是使用的StringBuilder(线程不安全)进行拼接的。所以,最后的值是StringBuilder的toString方法转出来的,该方法为new String;其地址并未指向自字符串常量池中。而final修饰的字符串,它值的引用,一直是指向字符串常量池!
  3. a+1==b+1//false a+1是StringBuilder的toString,底层代码如下;
 @Override
    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }

所以实际a+1等价于new String(“abc1”)。常量池中如果没有abc1,则在new的String对象操作中,会先把abc1放入池中,再返回该值的引用,而栈中变量,则指向的是new String在堆中的地址,new String内部的char数组则指向的常量池地址。而b+1则是直接指向了地址中已经存在的abc1字段的地址;
a+1== c// c直接指向字符串常量池地址 a+1如上所述
b+1== c //b+1是直接指向字符串常量池中"abc1"的地址,c也是指向该地址,所以为true

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值