澄清StringBuilder引用和方法执行顺序

时间:2015-07-26 09:15:06

标签: java reference stringbuilder

这段代码

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

将打印

    b1 = hello!hello!

因为首先执行内部append并修改对象b1;然后评估外部b1(它现在等于hello!)并且附加相同的字符串。所以

  1. 执行内部表达
  2. 原始对象被修改
  3. 外部表达式在修改后的对象上执行
  4. 但是现在,为什么这段代码会抛出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");
    

    并打印

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

    我希望引用s1指向s2 引用的对象,然后执行外部append

    在某种程度上,分配b1.append("!");会影响“外部”b1,但s1=s2.append("!")则不会。我知道这是因为在第一种情况下我正在修改对象,而在第二种情况下我正在修改引用但是......评估和执行值/引用/方法的顺序是什么?

    修改

    数组也是如此:

        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");
    

    打印

        y = [2, 0, 4]
    

        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));
    

    打印

        java.lang.NullPointerException
        x1 = [1, 2, 3]
        x2 = [1, 2, 3]
    

4 个答案:

答案 0 :(得分:2)

这在JLS 15.12.4。中指定:

  

如果form是ExpressionName。 [TypeArguments]标识符,然后:

     
      
  • 如果调用模式是静态的,则没有目标引用。计算ExpressionName,但结果是   丢弃。

  •   
  • 否则,目标参考是ExpressionName表示的值。

  •   

  

作为实例方法调用(第15.12节)的一部分,有一个   表达要调用的对象的表达式。这个表达   似乎在任何论证的任何部分之前都要完全评估   计算方法调用的表达式。

因此,行s1.append(s1=s2.append("!")); s1.append(s1 = ...)之前)首先在参数表达式s1=s2.append("!")之前进行求值。因此,在null更改为引用StringBuilder s1实例之前,会记住s2引用作为目标引用。

然后评估参数表达式,以便执行s1=s2.append("!")。但它之前记住了目标引用,因此在append引用上调用null方法,并且调用的结果抛出NullPointerException

答案 1 :(得分:1)

让我们看看你的例子中的字节代码,

   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

如果您仔细阅读我在代码中的注释,您就会知道已加载null以调用方法append()

让我们再举一个例子,

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

这只会打印world!。即使你期望world!world!

这是因为在为方法调用加载s1之后,你正在重新定义def has_duplicates(t): return any(t.count(i)>1 for i in t) lst = [1, 2, 3, 4, 5, 6, 1, 5 ,6] print(has_duplicates(lst)) 的值。这意味着在方法调用中,重写的值将被覆盖。

答案 2 :(得分:1)

Java解释器首先尝试定位(不评估,只是定位)该方法,在本例中为s1.append()。我的猜测是它将方法指针添加到堆栈中。要做到这一点,它需要知道对象s1的确切类,因此它取消引用它。由于s1为空,因此会产生NullPointerException

甚至在评估参数之前就会发生这种情况,因此s1仍然是null

This SO answer列出了s1.append来电中出现的不同步骤:

  
      
  1. 对象指针用于引用对象,并从那里引用Class对象。

  2.   
  3. 方法指针位于Class对象中。 (将方法名称转换为方法索引的查找主要在类时完成   加载了,所以这基本上只是一个数组索引操作。)

  4.   
  5. 通常某种&#34;标记&#34;被推到JVM堆栈上。这将包含调用者的指令指针和指向该指针的指针   他的堆栈的基础。 (这里有很多不同的实现。)

  6.   
  7. 查看方法的定义以查看需要多少本地变量。许多空白元素被压入堆栈。

  8.   
  9. 对象(&#34; this&#34;)指针存储在本地var 0中,任何parms都存储在1,2,3 ......中。

  10.   
  11. 控件转移到被调用的方法。

  12.   

NullPointerException发生在第1步。

答案 3 :(得分:0)

b1.append(b1.append("!"));中的

并非内在追加它首先被执行。 Java将首先调用append,然后将为此调用评估参数b1.append("!"),该调用将修改b1对象。这里s1.append(s1=s2.append("!"));会打电话 s1.append()方法,但由于s1为空,会抛出NullPointerException