• 通过Java字节码发现有趣的内幕之String篇(上)(转)


    原文出处: jaffa

    很多时候我们在编写Java代码时,判断和猜测代码问题时主要是通过运行结果来得到答案,本博文主要是想通过Java字节码的方式来进一步求证我们已知的东西。这里没有对Java字节码知识进行介绍,如果想了解更多的Java字节码或对其感兴趣的朋友可以先阅读字节码基础:JVM字节码初探

    String字面量可以通过’==’判断两个字符串是否相同,是因为大家都知道’==’是用来判断两个对象的值引用地址是否一致,两个值一样的字符串字面量定义是否指向同一个值内存地址呢?答案是肯定的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package com.jaffa.test.string;
     
    public class ConstPoolTest {
        public static void main(String[] args){
            String str1 = "strVal_1";
            String str2 = "strVal_1";
            //print str1==str2 is true
            System.out.printf("str1==str2 is %b",str1==str2);
        }
    }

    代码中声明了str1和str2的字面量值都为strVal_1,并且打印出str1==str2为true,说明两个str1和str2变量同时指向同一个字符串常量值的内存地址,下面通过Java字节码来验证这个结果。

    在命令行我们通过javap工具来查看一个class文件的字节码。

    1
    javap -v com.jaffa.test.string.ConstPoolTest

    在Constant pool列表中看到#16为一个String类型并值指向#17,而#17是一个utf8字符集编码值为strVal_1,所以#16和#17最终表达就是在常量池中有个String类型值为strVal_1的常量数据。

    那接下来需要确认str1和str2两个变量值是否都是指向#16呢?

    1
    2
    3
    4
    5
    //ldc表示将一个常量加载到操作数栈
     0: ldc           #16     //将#16对应的常量值加载到操作数栈中           
     2: astore_1              //将当前操作数栈中赋于变量1,即str1
     3: ldc           #16     //再次将#16对应的常量值加载到操作数栈中
     5: astore_2              //将当前操作数栈中赋于变量2,即str2

    从上面字节码执行来看,str1和str2都是被赋于同一个常量值,由此可以得出两个变更指向同一个内存地址。

    通过同样的方式,我们来看一下如果是非字面量的情况会是怎么样的,Java代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package com.jaffa.test.string;
     
    public class ConstPoolTest {
        public static void main(String[] args){
            String str1 = "strVal_1";
            String str2 = new String("strVal_1");
     
            System.out.printf("str1==str2 is %b",str1==str2);
        }
    }

    上面代码输出结果为false,通过javap查看发现str2变量的字节码指令发生了变化,如下现两截图:

    1
    2
    3
    4
    5
    6
    7
    8
    //ldc表示将一个常量加载到操作数栈
     0: ldc           #16     //将#16对应的常量值加载到操作数栈中           
     2: astore_1              //将当前操作数栈中赋于变量1,即str1
     3: new           #18     //创建了一个类实例,#18指向一个实例类型String
     6: dup                   //配置上行完成操作栈指令
     7: ldc           #16     //将#16对应的常量值加载到操作数栈中
     9: invokespecial #20     //调用String实例初始化方法,并将#16输入
    12: astore_2              //将new出来的实例赋于变量2,即str2

    这时str2变量是创建一个新的内存地址,而非直接指向#16常量内存地址,所以str1==str2的结果为false。同时可以看到通过new String()带来看更多的字节码指令操作,运行上花费了更多的系统资源。

    本系列:

    http://www.importnew.com/18785.html

  • 相关阅读:
    测试理论
    字符串
    类的无参方法
    类和对象
    数组
    循环结构
    选择结构
    java——面对对象
    android通知的基本用法
    Git的基本使用
  • 原文地址:https://www.cnblogs.com/softidea/p/5350898.html
Copyright © 2020-2023  润新知