Java 表示式之謎:為什麼 index 增加了兩次?
Code Golf中的一位挑戰者在比賽中寫了下面這段程式碼:(譯註:Code Golf是一個程式設計挑戰比賽,提交的程式碼越短越好)
import java.util.*;
public
class Main {
public
static
void
main
(String[] args) {
int size =
3;
String[]
array =
new String[size];
Arrays.fill(
array,
"");
for(
int i =
0; i <=
100; ) {
array[i++%size] += i +
" ";
}
for(String element:
array) {
System.out.println(element);
}
}
}
在Java 8中執行程式碼,得到結果如下:
1
4
7
10
13
16
19
22
25
28
31
34
37
40
43
46
49
52
55
58
61
64
67
70
73
76
79
82
85
88
91
94
97
100
2
5
8
11
14
17
20
23
26
29
32
35
38
41
44
47
50
53
56
59
62
65
68
71
74
77
80
83
86
89
92
95
98
101
3
6
9
12
15
18
21
24
27
30
33
36
39
42
45
48
51
54
57
60
63
66
69
72
75
78
81
84
87
90
93
96
99
在Java 10中執行程式碼,得到結果如下:
2
4
6
8
10
12
14
16
18
20
22
24
26
28
30
32
34
36
38
40
42
44
46
48
50
52
54
56
58
60
62
64
66
68
70
72
74
76
78
80
82
84
86
88
90
92
94
96
98
2
4
6
8
10
12
14
16
18
20
22
24
26
28
30
32
34
36
38
40
42
44
46
48
50
52
54
56
58
60
62
64
66
68
70
72
74
76
78
80
82
84
86
88
90
92
94
96
98
100
102
2
4
6
8
10
12
14
16
18
20
22
24
26
28
30
32
34
36
38
40
42
44
46
48
50
52
54
56
58
60
62
64
66
68
70
72
74
76
78
80
82
84
86
88
90
92
94
96
98
100
在Java 10中編號似乎完全失效了。這中間發生了什麼?這是Java 10的bug嗎?
來自評論區的討論:
用Java 9或更高版本編譯會出現問題(我們在Java 10中找到了問題)。在Java 8上編譯這段程式碼,然後在Java 9或更高版本(包括Java 11 EA)中執行,可以得到預期結果。
雖然這種程式碼不標準,但符合Java規範。Kevin Cruijssen在一個Code Golf挑戰中發現了這個問題,看起來結果很奇怪。
Didier L發現可以用更短、更容易理解的程式碼重現該問題:
class Main {
public
static
void
main
(String[] args) {
String[]
array = {
"" };
array[test()] +=
"a";
}
static
int
test
() {
System.out.println(
"evaluated");
return
0;
}
}
用Java 8編譯,執行結果:
evaluated
用Java 9和10編譯,執行結果
evaluated
evaluated
問題似乎與字串連線操作和賦值運算子( += )有關,當作為左運算子時會出現副作用,例如 array[test()]+="a" 、 array[ix++]+="a" 、 test()[index]+="a" 或 test().field+="a" 。字串連線要求至少有一邊的物件型別為String。其他型別或結構無法復現該錯誤。
答案
這是JDK 9開始引入的一個javac bug(疑似在字串拼接過程中進行了修改),已由javac團隊確認,bug id JDK-8204322。檢視該行對應的位元組碼:
array[i++%size] += i + " ";
位元組碼:
21: aload_2
22: iload_3
23: iinc
3,
1
26: iload_1
27: irem
28: aload_2
29: iload_3
30: iinc
3,
1
33: iload_1
34: irem
35: aaload
36: iload_3
37: invokedynamic #
5,
0
// makeConcatWithConstants:(Ljava/lang/String;I)Ljava/lang/String;
42: aastore
最後的 aaload 從陣列中實際載入資料。但是,下面這段
21
: aload_2
// load 陣列引用
22
: iload_3
// load 'i'
function(){ //外匯跟單
23
: iinc
3
,
1
// 'i' 加1 (不影響已載入的陣列值)
26
: iload_1
// load 'size'
27
: irem
// 計算餘數
基本上能與 array[i++%size] 表示式對應(去掉實際的load和store),問題是這裡出現了兩次。 按照jls-15.26.2規範中的描述,這是不正確的:
複合表示式 E1 op= E2 與 E1 = (T) ((E1) op (E2)) 等價,其中T的型別是E1, 除了E1應該只執行一次。
因此,表示式 array[i++%size] += i + " "; 中 array[i++%size] 應該只計算一次。 但是這裡會計算兩次(load一次,store一次)。
可以確認,這是一個bug。
更新:
該bug已在JDK 11中修復,並且對應更新到JDK 10(但JDK 9不會修復,因為它不再進行public updates)。
Aleksey ShipilevJBS 頁面上提到(@DidierL在此進行了評論):
解決方法 :使用 -XDstringConcat=inline 編譯。
這樣會使用 StringBuilder 進行字串連線,不會出現該bug。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69946337/viewspace-2663017/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Java之lambda表示式Java
- 為什麼正規表示式的test()函式總是返回true函式
- java正規表示式之 groupJava
- TCP 兩次握手為什麼無法阻止歷史連線?TCP
- 同一份資料,Redis為什麼要存兩次Redis
- 什麼是IIFE(立即呼叫函式表示式)?函式
- 【譯】java8之lambda表示式Java
- Java解惑五:類之謎Java
- index為什麼可能會比table大很多Index
- 為什麼有時候spring mvc的interceptor會執行兩次SpringMVC
- java8 新特性之Lambda 表示式Java
- Java8 新特性之 Lambda 表示式Java
- Lambda表示式之爭:Scala vs Java 8Java
- BC搭建為什麼需要API介面?api介面增加了那些好處?API
- 為什麼Scrum沒有幫助我反而增加了工作量? - RedditScrum
- 定義陣列時為什麼不能用含有變數的表示式陣列變數
- 為什麼登陸ERP的輸入兩次使用者密碼?密碼
- Java | Lambda表示式Java
- Lambda表示式(Java)Java
- java lambda 表示式Java
- Java Lambda表示式Java
- java8新特性之lambda表示式(一)Java
- 中綴表示式轉為逆波蘭表示式
- 我為什麼使用 JavaJava
- 【Java面試】TCP協議為什麼要設計三次握手?Java面試TCP協議
- 一次性搞懂JavaScript正規表示式之方法JavaScript
- 一次性搞懂JavaScript正規表示式之引擎JavaScript
- [轉]Java 8 的 lambda 表示式 Java 8 的 lambda 表示式Java
- 趣文:TCP 握手為什麼是 3 次,2 次或 4 次不行麼?TCP
- 深入理解redux之reducer為什麼是純函式Redux函式
- C++之類解構函式為什麼是虛擬函式C++函式
- 什麼是java?為什麼大家都學習java技術?Java
- 正規表示式獲取兩個字元之間的字串資訊字元字串
- java中cron表示式 每10分鐘執行一次Java
- 兩次獲獎,Steam特別好評,為什麼玩家會說這款遊戲很酷?遊戲
- MySQL中為什麼要使用索引合併(Index Merge)?MySql索引Index
- 一次性搞懂JavaScript正規表示式之語法JavaScript
- Java的Lambda表示式Java