这段代码
StringBuilder b1=new StringBuilder("hello");
b1.append(b1.append("!"));
System.out.println("b1 = "+b1);
将打印
b1 = hello!hello!
因为首先执行内部append
并修改对象b1
;然后评估外部b1
(它现在等于hello!
)并且附加相同的字符串。所以
但是现在,为什么这段代码会抛出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]
答案 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
来电中出现的不同步骤:
对象指针用于引用对象,并从那里引用Class对象。
方法指针位于Class对象中。 (将方法名称转换为方法索引的查找主要在类时完成 加载了,所以这基本上只是一个数组索引操作。)
通常某种&#34;标记&#34;被推到JVM堆栈上。这将包含调用者的指令指针和指向该指针的指针 他的堆栈的基础。 (这里有很多不同的实现。)
查看方法的定义以查看需要多少本地变量。许多空白元素被压入堆栈。
对象(&#34; this&#34;)指针存储在本地var 0中,任何parms都存储在1,2,3 ......中。
- 醇>
控件转移到被调用的方法。
NullPointerException
发生在第1步。
答案 3 :(得分:0)
b1.append(b1.append("!"));
中的并非内在追加它首先被执行。 Java将首先调用append,然后将为此调用评估参数b1.append("!")
,该调用将修改b1
对象。这里s1.append(s1=s2.append("!"));
会打电话
s1.append()
方法,但由于s1
为空,会抛出NullPointerException
。