An explanation of the execution order of the links and StringBuilder methods

This piece of code

StringBuilder b1=new StringBuilder("hello"); b1.append(b1.append("!")); System.out.println("b1 = "+b1); 

will print

  b1 = hello!hello! 

because the internal append is executed first and modifies the b1 object; then the outer b1 is evaluated (now it is hello! ), and the same line is added to it. So

  • an internal expression is executed
  • source object changed
  • external expression is executed on the modified object

But now, why does this code throw a NullPointerException ?

  StringBuilder s1=null; StringBuilder s2=new StringBuilder("world"); try{s1.append(s1=s2.append("!"));} catch(Exception e){System.out.println(e);} System.out.println("s1 = "+s1+"\ns2 = "+s2+"\n"); 

and seal

  java.lang.NullPointerException s1 = world! s2 = world! 

I was expecting the s1 reference to point to the object s2 referencing before executing the external append .

In some ways, the purpose of b1.append("!"); affects the "external" b1 , but s1=s2.append("!") not. I know this because in the first case I change the object, and in the second I change the link, but ... what is the order in which values โ€‹โ€‹/ references / methods are evaluated and executed?

Edit

The same thing happens with arrays:

  int[] y = { 0, 0, 0 }; try {y[y[0] = 2] = 4;} catch (Exception e) {System.out.println(e);} System.out.println("y = "+Arrays.toString(y)+"\n"); 

prints

  y = [2, 0, 4] 

while

  int[] x1 = null; int[] x2 = { 1, 2, 3 }; try {x1[(x1=x2)[0]] = 0;} catch (Exception e) {System.out.println(e);} System.out.println("x1 = "+Arrays.toString(x1)+"\nx2 = "+Arrays.toString(x2)); 

prints

  java.lang.NullPointerException x1 = [1, 2, 3] x2 = [1, 2, 3] 
+6
source share
4 answers

This is stated in JLS 15.12.4 .:

If the form is an ExpressionName expression. Identifier [TypeArguments], then:

  • If the call mode is static, then there is no target link. ExpressionName is evaluated, but then the result is discarded.

  • Otherwise, the target link is the value indicated by ExpressionName.

and

As part of calling the instance method (ยง15.12), there is an expression that denotes the called object. This expression appears to be fully evaluated before any part of any argument of the expression to invoke the method is evaluated.

So, in the line s1.append(s1=s2.append("!")); s1 (before .append(s1 = ...) ) the expression s1=s2.append("!") is evaluated first. Thus, the null reference is remembered as the target reference before s1 changed to refer to an instance of StringBuilder s2 .

Then the argument expression is evaluated so that s1=s2.append("!") . But he remembered the destination link before, so the append method is called in a null reference, and the result of the call throws a NullPointerException .

+2
source

Let's look at the bytecode in your example,

  0: aconst_null 1: astore_1 // Comment: null is stored to s1. 2: new #18 // class java/lang/StringBuilder 5: dup 6: ldc #20 // String world 8: invokespecial #22 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 11: astore_2 // Comment: new StringBuilder is stored to s2. 12: aload_1 // Comment: s1 (which is null) is loaded for method call. 13: aload_2 // Comment: s2 is loaded for method call. 14: ldc #25 // String ! 16: invokevirtual #27 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19: dup 20: astore_1 // Comment: s2.append() return value is stored in s1. 21: invokevirtual #31 // Method java/lang/StringBuilder.append:(Ljava/lang/CharSequence;)Ljava/lang/StringBuilder; // Comment: append() method is called on already loaded s1 value (which is null). 24: pop 25: return 

If you read my comments in code, you will know that null loaded to call the append() method.

Take another example:

  StringBuilder s1 = new StringBuilder(); StringBuilder s2 = new StringBuilder("world"); s1.append(s1 = s2.append("!")); System.out.println(s1); 

It will print only world! . Even if you expect world!world! .

This is because you assure the value of s1 after loading it to call the method. This means that when the method is called, the overridden value will be overwritten.

+1
source

What happens is that the Java interpreter first tries to find (not evaluate, just find) a method, in this case s1.append() . I assume it pushes a method pointer onto the stack. To do this, he needs to know the exact class of the object s1 , so he plays it. Since s1 is null, this results in a NullPointerException .

This happens before the arguments are evaluated, which means that s1 is still null .

This SO answer contains various steps that occur in our s1.append call:

  • The object pointer is used to refer to the object, and from there the class object.

  • The method pointer is in the Class object. (The search to convert the method name to the method index was basically done when the class was loaded, so this is basically just an array index operation.)

  • Typically, some kind of "character" is pushed onto the JVM stack. This will contain the caller's instruction pointer and a pointer to the bases of its stack. (There are many different implementations.)

  • The definition of the method is analyzed to find out how many local vars are needed. Many empty items are pushed onto the stack.

  • An object pointer ("this") is stored in local var 0, and any parms are stored in 1,2,3 ... as needed.

  • Control is passed to the called method.

NullPointerException occurs in step 1.

+1
source

in b1.append(b1.append("!")); not the internal application that it executed first. Java will call the first application and then evaluate the b1.append("!") Parameter for this call, which will change the b1 object. Here s1.append(s1=s2.append("!")); will call s1.append() , but since s1 is null, a NullPointerException will be NullPointerException .

0
source

All Articles