Regarding object splicing in String creation, how many objects are created and whether StringBuilder is used. There are also poisonous points of decompilation

First of all, I am a newbie in programming, but I am not a complete novice either. I am just getting started. Regarding the question of how many objects are created by String, I strongly recommend that you read this article first:

Java interview, how many String objects were created? I’ll let you ask! Let you ask! Let you ask! _Small Target Youth’s Blog-CSDN Blog

Please read the above article first, or have some understanding of several objects created by String before reading this article! ! ! Or if you find that the decompilation result is different from what other bloggers described during your own decompilation process, you can continue to check it (or you are welcome if you just want to see it!)

This article mainly gives a brief explanation of the big pitfall of “decompilation”, and briefly introduces the role of disassembly. The main method and class declaration are omitted in my code, so that the code will be simpler. Just know that the following code is in the main method.

First look at the following code:

String a = "a";
String b = "b";
String c = "c";
String str1 = a + b + c;

System.out.println(str1 == a + b + c);
System.out.println(a + b + c);
System.out.println(str1);

The class file is obtained after compilation through CMD’s javac, and decompiled through jadx software as follows:

String str = "abc";
System.out.println(str == new StringBuilder().append("a").append("b").append("c").toString());
System.out.println("abc");
System.out.println(str);

Then similarly, the class file compiled using eclipse is decompiled through jadx software as follows:

String str1 = String.valueOf("a") + "bc";
System.out.println(str1 == new StringBuilder(String.valueOf("a")).append("b").append("c").toString());
System.out.println(String.valueOf("a") + "bc");
System.out.println(str1);

It can be seen that the two decompilation results are inconsistent, indicating that there are differences between the class compiled through CMD and the class file compiled in Eclipse. I also specifically confirmed that the jdk and cmd used by eclipse are one jdk, both 1.8 (one path).

Originally, I only used CMD for compilation and decompilation to verify and understand the StringBuilder replacement mechanism when splicing String objects (that is, under what circumstances the String object splicing will be replaced by StringBuilder), and I thought I had summarized it more than the boss. Few conclusions. For example, the following code:

String a = "a";
String b = "b";
String c = "c";
String str1 = a + b + c;
System.out.println(str1);

After decompiling through jadx software:

System.out.println("abc");

Yes, you read that right. This is the only line of code left. For the first time, or for more than a day, when I saw this decompilation result, I always thought: “JVM has optimized the simple code. Not all objects participating in the + sign splicing will become StringBuilder.”

But suddenly when I was testing the following code, I found something was wrong. The decompiled code was completely different from the source code:

String s1 = "a";
String s2 = "b";
String s3 = new String("b");
String s4 = s1 + s3;
String s5="ab";
String s6 = s1 + s2;
String s66= s1 + s2;
String s7 = "a" + s2;
String s8 = s1 + "b";
String s9 = "a" + "b";

System.out.println(s2 == s3);
System.out.println(s4 == s5);
System.out.println(s4 == s6);
System.out.println(s6 == s66);
System.out.println(s5 == s7);
System.out.println(s5 == s8);
System.out.println(s7 == s8);
System.out.println(s9 == s8);

Its decompilation result is as follows:

String str = new String("b");
String str2 = "a" + str;
String str3 = "ab";
String str4 = "ab";
String str5 = "ab";
String str6 = "ab";
System.out.println("b" == str);
System.out.println(str2 == "ab");
System.out.println(str2 == str3);
System.out.println(str3 == str4);
System.out.println("ab" == str5);
System.out.println("ab" == str6);
System.out.println(str5 == str6);
System.out.println("ab" == str6);

If you are interested, you can try running it yourself, but in short, the output results are inconsistent!

Then I studied for a long time and through dozens of tests, I found that there was a problem with this decompilation. I’m not sure if this happens with other types of code, but the == operator does cause logical inconsistencies after decompilation.

I went to Baidu to search, and then I learned that Java also has disassembly! So I started to verify again. Although I couldn’t understand what the disassembled thing was, I searched and looked at the number of times StringBuilder was created (I directly searched and called init several times) and determined the final correct answer:

“As long as there is an object involved in the + sign splicing, then this code will definitely be optimized into a StringBuilder.”

Okay, let’s do a simple verification. I won’t list the new code here. Do you still remember the example at the beginning? It doesn’t matter if you don’t remember, as follows:

//Source code
String a = "a";
String b = "b";
String c = "c";
String str1 = a + b + c;

System.out.println(str1 == a + b + c);
System.out.println(a + b + c);
System.out.println(str1);


//CMD compilation, jadx decompilation
String str = "abc";
System.out.println(str == new StringBuilder().append("a").append("b").append("c").toString());
System.out.println("abc");
System.out.println(str);


//Eclipse compilation, jadx decompilation
String str1 = String.valueOf("a") + "bc";
System.out.println(str1 == new StringBuilder(String.valueOf("a")).append("b").append("c").toString());
System.out.println(String.valueOf("a") + "bc");
System.out.println(str1);

Okay, we first see the decompiled code after CMD compilation. The code in the fourth line becomes:

System.out.println(“abc”);

If we only look at this line of code, “abc” here is stored in the string constant pool, so we will mistakenly think that as mentioned before, “JVM has optimized simple code. Not all objects participate in the + sign The splicing will turn into StringBuilder.”

But is this really the case? Here, we introduce Java disassembly.

Open the CMD command line window. Taking me as an example, I placed the class file on the desktop of the C drive. And my username is Admin , so the first instruction is as follows:

cd C:\Users\Admin\Desktop

It means jumping to the C:\Users\Admin\Desktop path. This makes it easier to operate files under this path. Then the name of my class file is Demo.class, so the disassembly instruction is:

javap -c Demo.class

If you don’t enter -c, it will be decompiled, but I don’t know why my decompiled main method is empty inside. That’s why I used jadx to decompile. That might be it, but it doesn’t matter. My understanding of disassembly is that this thing should be the actual execution order and content of the class file. I am not sure about this. If I understand and update it, I will explain it in the comment area or modify the article.

Extra note, if you need to perform disassembly frequently, you can write a script. First create a new text document, change the suffix .txt to .bat, and then name it whatever you want. Right-click this .bat file, select Edit or Open With – Open with Notepad, and then enter the following code (just a case. & amp; & amp; modify the previous path by yourself)

@echo off

start cmd /k "cd /d C:\Users\Admin\Desktop & amp; & amp;del Demo.class & amp; & amp;javac Demo.java"

Back to the topic, the code after disassembling the CMD compiled class file is as follows:

public class Demo {
  public Demo();
    Code:
       0: aload_0
       1: invokespecial #1 // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc #2 // String a
       2: astore_1
       3: ldc #3 // String b
       5: astore_2
       6: ldc #4 // String c
       8: astore_3
       9: new #5 // class java/lang/StringBuilder
      12: dup
      13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
      16: aload_1
      17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      20: aload_2
      21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      24: aload_3
      25: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      28: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      31: astore 4
      33: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
      36: load 4
      38: new #5 // class java/lang/StringBuilder
      41: dup
      42: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
      45: aload_1
      46: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      49: aload_2
      50: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      53: aload_3
      54: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      57: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      60: if_acmpne 67
      63: iconst_1
      64: goto 68
      67: iconst_0
      68: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
      71: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
      74: new #5 // class java/lang/StringBuilder
      77: dup
      78: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
      81: aload_1
      82: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      85: aload_2
      86: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      89: aload_3
      90: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      93: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      96: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      99: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
     102: load 4
     104: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     107: return
}

As you can see, in this disassembly code, new appears three times in total, and the following comments declare StringBuilder. It just corresponds to three places in the source code that “may involve changing StringBuilder”. Why is it possible? Because I can’t understand this thing, but from the surface, it should be like this. Don’t worry about “this blogger can’t understand it himself, can what he said be true?”, as evidenced by examples, you can treat me as providing verification ideas, and you can verify it yourself after reading it.

So let’s do another experiment. Remove the “System.out.println(a + b + c);” code from the source code, then compile it into a class file and disassemble it, as follows:

//Source code
String a = "a";
String b = "b";
String c = "c";
String str1 = a + b + c;

System.out.println(str1 == a + b + c);
System.out.println(str1);


//Disassembly
public class Demo {
  public Demo();
    Code:
       0: aload_0
       1: invokespecial #1 // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc #2 // String a
       2: astore_1
       3: ldc #3 // String b
       5: astore_2
       6: ldc #4 // String c
       8: astore_3
       9: new #5 // class java/lang/StringBuilder
      12: dup
      13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
      16: aload_1
      17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      20: aload_2
      21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      24: aload_3
      25: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      28: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      31: astore 4
      33: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
      36: load 4
      38: new #5 // class java/lang/StringBuilder
      41: dup
      42: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
      45: aload_1
      46: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      49: aload_2
      50: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      53: aload_3
      54: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      57: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      60: if_acmpne 67
      63: iconst_1
      64: goto 68
      67: iconst_0
      68: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
      71: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
      74: load 4
      76: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      79: return
}

It can be seen that when only “System.out.println(a + b + c);” is removed, StringBuilder is obviously created from three times to two times, indicating that the removed code does indeed have a transformation of StringBuilder. Similar case 2 is as follows:

//Source code
String a = "a";
String b = a + "bc";
System.out.println(a + "bc");
System.out.println(b);


//Decompile
System.out.println("abc");
System.out.println("abc");



//Disassembly
public class Demo {
  public Demo();
    Code:
       0: aload_0
       1: invokespecial #1 // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc #2 // String a
       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 bc
      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_2
      23: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
      26: new #3 // class java/lang/StringBuilder
      29: dup
      30: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
      33: aload_1
      34: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      37: ldc #6 // String bc
      39: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      42: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      45: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      48: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
      51: aload_2
      52: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      55: return
}

I have done two similar tests, covering basically all kinds of situations. So the conclusion is:

“As long as an object participates in the + sign splicing, it will definitely be optimized into a StringBuilder by the JVM!”

“Don’t decompile to verify the code logic if you have nothing to do. At least don’t use jadx to verify the code logic. Use disassembly more~”

OK, that’s it for this chapter. Hope it helps you