String类的对象创建问题

854 阅读2分钟

注:本专栏文章均为本人原创,未经本人授权请勿私自转载,谢谢。

以下是源代码分析:

public static void main(String[] args) {
    // 初始化时常量池无该字符串缓存,则在常量池创建一个实例;同时,new 操作符在堆内存也创建一个实例;
    String str1 = new String("ABCDEFG"); 
    String str2 = "ABCDEFG"; // 直接从常量池中查找,不创建
    String str3 = "ABCDEFG"; // 直接从常量池中查找,不创建
    System.out.println(str2 ==  str3); // true
    System.out.println(str1 ==  str2); // false
    System.out.println(str1.equals(str2)); // true
    System.out.println(51 == 51.0); // 基本类型比较值
}

以下是字节码分析:

【说明】:有些操作符后面带有#n,这是在Constant Pool中所分配的序号,主要是为了减少网络传输的流量和时间,故用序号代替。
 
 0: new #2 <java/lang/String>        // new 一个 String 类型的对象
 3: dup                              // 复制类实例引用并压入栈顶(标记为待处理)
 4: ldc #3 <ABCDEFG>                 // 将字符串从常量池加载到操作数栈(由于该类的构造函数中传入了 ABCDFEG 常量,此时常量池中的字符串已创建,故需要从常量池加载)
 6: invokespecial #4 <java/lang/String.<init>> // 执行构造方法
 9: astore_1                         // 将该值存为变量 1
10: ldc #3 <ABCDEFG>                 // 直接从常量池加载 ABCDFEG 常量
12: astore_2                         // 将该值存为变量 2
13: ldc #3 <ABCDEFG>                 // 直接从常量池加载 ABCDFEG 常量
15: astore_3                         // 将该值存为变量 3

// 从这里开始是第一个比较逻辑
16: getstatic #5 <java/lang/System.out> 
19: aload_2
20: aload_3
21: if_acmpne 28 (+7) // 若变量 2 和变量 3 相等,则继续执行,否则跳到 28
24: iconst_1          // 相等时:加载变量 1(true)
25: goto 29 (+4)      // 跳到 29
28: iconst_0          // 不相等时:加载变量 0(false)
29: invokevirtual #6 <java/io/PrintStream.println> // 打印该变量

// 从这里开始是第二个比较逻辑,与上个相仿,就不做讨论了
32: getstatic #5 <java/lang/System.out>
35: aload_1
36: aload_2
37: if_acmpne 44 (+7)
40: iconst_1
41: goto 45 (+4)
44: iconst_0
45: invokevirtual #6 <java/io/PrintStream.println>
48: getstatic #5 <java/lang/System.out>
51: aload_1
52: aload_2
53: invokevirtual #7 <java/lang/String.equals>
56: invokevirtual #6 <java/io/PrintStream.println>

// 从这里开始是最后的数字比较,可以看到 Java 直接将 结果优化成了 true
59: getstatic #5 <java/lang/System.out> 
62: iconst_1
63: invokevirtual #6 <java/io/PrintStream.println>
66: return

另外,对于以下代码,strRes1和strRes2的反编译代码截然不同:

public static void main(String[] args) {
    /*final*/ String str1 = "abc";
    /*final*/ String str2 = "def";
    String strRes1 = "abc" + "def"; // String strRes1 = "abcdef";
    String strRes2 = str1 + str2; // String strRes2 = new StringBuilder().append(str1).append(str2).toString();
    System.out.println(strRes1 + ":" + strRes2);
}

当某字符串等于两字符串常量相加时,java编译器会将其自动优化为两个字符串常量的和;而当某字符串等于两字符串变量相加时,java编译器不会进行优化。

也就是,当str1和str2都为final类型的字符串时,strRes2的反编译代码也会变为String strRes2 = "abcdef";