這里因?yàn)?/span>param要在匿名內(nèi)部類的print()方法中使用,因而它要用final修飾;local/local2是局部變量,因而也需要final修飾;而field是外部類MyApplication的字段,因而不需要final修飾。這種設(shè)計是基于什么理由呢?
我想這個問題應(yīng)該從Java是如何實(shí)現(xiàn)匿名內(nèi)部類的。其中有兩點(diǎn):
1. 匿名內(nèi)部類可以使用外部類的變量(局部或成員變來那個)
2. 匿名內(nèi)部類中不同的方法可以共享這些變量
根據(jù)這兩點(diǎn)信息我們就可以分析,可能這些變量會在匿名內(nèi)部類的字段中保存著,并且在構(gòu)造的時候?qū)⑺麄兊闹?/span>/引用傳入內(nèi)部類。這樣就可以保證同時實(shí)現(xiàn)上述兩點(diǎn)了。
事實(shí)上,Java就是這樣設(shè)計的,并且所謂匿名類,其實(shí)并不是匿名的,只是編譯器幫我們命名了而已。這點(diǎn)我們可以通過這兩個類編譯出來的字節(jié)碼看出來:
1 // Compiled from Printer.java (version 1.6 : 50.0, super bit)
2 class levin.test.anonymous.MyApplication$1 implements levin.test.anonymous.Printer {
3
4 // Field descriptor #8 Llevin/test/anonymous/MyApplication;
5 final synthetic levin.test.anonymous.MyApplication this$0;
6
7 // Field descriptor #10 J
8 private final synthetic long val$local2;
9
10 // Field descriptor #12 Ljava/lang/Integer;
11 private final synthetic java.lang.Integer val$param;
12
13 // Method descriptor #14 (Llevin/test/anonymous/MyApplication;JLjava/lang/Integer;)V
14 // Stack: 3, Locals: 5
15 MyApplication$1(levin.test.anonymous.MyApplication arg0, long arg1, java.lang.Integer arg2);
16 0 aload_0 [this]
17 1 aload_1 [arg0]
18 2 putfield levin.test.anonymous.MyApplication$1.this$0 : levin.test.anonymous.MyApplication [16]
19 5 aload_0 [this]
20 6 lload_2 [arg1]
21 7 putfield levin.test.anonymous.MyApplication$1.val$local2 : long [18]
22 10 aload_0 [this]
23 11 aload 4 [arg2]
24 13 putfield levin.test.anonymous.MyApplication$1.val$param : java.lang.Integer [20]
25 16 aload_0 [this]
26 17 invokespecial java.lang.Object() [22]
27 20 return
28 Line numbers:
29 [pc: 0, line: 1]
30 [pc: 16, line: 13]
31 Local variable table:
32 [pc: 0, pc: 21] local: this index: 0 type: new levin.test.anonymous.MyApplication(){}
33
34 // Method descriptor #24 ()V
35 // Stack: 4, Locals: 1
36 public void print();
37 0 getstatic java.lang.System.out : java.io.PrintStream [30]
38 3 ldc <String "Local value: 100"> [36]
39 5 invokevirtual java.io.PrintStream.println(java.lang.String) : void [38]
40 8 getstatic java.lang.System.out : java.io.PrintStream [30]
41 11 new java.lang.StringBuilder [44]
42 14 dup
43 15 ldc <String "Local2 value: "> [46]
44 17 invokespecial java.lang.StringBuilder(java.lang.String) [48]
45 20 aload_0 [this]
46 21 getfield levin.test.anonymous.MyApplication$1.val$local2 : long [18]
47 24 invokevirtual java.lang.StringBuilder.append(long) : java.lang.StringBuilder [50]
48 27 invokevirtual java.lang.StringBuilder.toString() : java.lang.String [54]
49 30 invokevirtual java.io.PrintStream.println(java.lang.String) : void [38]
50 33 getstatic java.lang.System.out : java.io.PrintStream [30]
51 36 new java.lang.StringBuilder [44]
52 39 dup
53 40 ldc <String "Parameter: "> [58]
54 42 invokespecial java.lang.StringBuilder(java.lang.String) [48]
55 45 aload_0 [this]
56 46 getfield levin.test.anonymous.MyApplication$1.val$param : java.lang.Integer [20]
57 49 invokevirtual java.lang.StringBuilder.append(java.lang.Object) : java.lang.StringBuilder [60]
58 52 invokevirtual java.lang.StringBuilder.toString() : java.lang.String [54]
59 55 invokevirtual java.io.PrintStream.println(java.lang.String) : void [38]
60 58 getstatic java.lang.System.out : java.io.PrintStream [30]
61 61 new java.lang.StringBuilder [44]
62 64 dup
63 65 ldc <String "Field value: "> [63]
64 67 invokespecial java.lang.StringBuilder(java.lang.String) [48]
65 70 aload_0 [this]
66 71 getfield levin.test.anonymous.MyApplication$1.this$0 : levin.test.anonymous.MyApplication [16]
67 74 invokestatic levin.test.anonymous.MyApplication.access$0(levin.test.anonymous.MyApplication) : int [65]
68 77 invokevirtual java.lang.StringBuilder.append(int) : java.lang.StringBuilder [71]
69 80 invokevirtual java.lang.StringBuilder.toString() : java.lang.String [54]
70 83 invokevirtual java.io.PrintStream.println(java.lang.String) : void [38]
71 86 return
72 Line numbers:
73 [pc: 0, line: 16]
74 [pc: 8, line: 17]
75 [pc: 33, line: 18]
76 [pc: 58, line: 19]
77 [pc: 86, line: 20]
78 Local variable table:
79 [pc: 0, pc: 87] local: this index: 0 type: new levin.test.anonymous.MyApplication(){}
80
81 Inner classes:
82 [inner class info: #1 levin/test/anonymous/MyApplication$1, outer class info: #0
83 inner name: #0, accessflags: 0 default]
84 Enclosing Method: #66 #77 levin/test/anonymous/MyApplication.print(Ljava/lang/Integer;)V
85 }
1 // Compiled from Printer.java (version 1.6 : 50.0, super bit)
2 class levin.test.anonymous.MyApplication {
3
4 // Field descriptor #6 I
5 private int field;
6
7 // Method descriptor #8 ()V
8 // Stack: 2, Locals: 1
9 MyApplication();
10 0 aload_0 [this]
11 1 invokespecial java.lang.Object() [10]
12 4 aload_0 [this]
13 5 bipush 10
14 7 putfield levin.test.anonymous.MyApplication.field : int [12]
15 10 return
16 Line numbers:
17 [pc: 0, line: 7]
18 [pc: 4, line: 8]
19 [pc: 10, line: 7]
20 Local variable table:
21 [pc: 0, pc: 11] local: this index: 0 type: levin.test.anonymous.MyApplication
22
23 // Method descriptor #19 (Ljava/lang/Integer;)V
24 // Stack: 6, Locals: 7
25 public void print(java.lang.Integer param);
26 0 ldc2_w <Long 100> [20]
27 3 lstore_2 [local]
28 4 aload_1 [param]
29 5 invokevirtual java.lang.Integer.longValue() : long [22]
30 8 ldc2_w <Long 100> [20]
31 11 ladd
32 12 lstore 4 [local2]
33 14 new levin.test.anonymous.MyApplication$1 [28]
34 17 dup
35 18 aload_0 [this]
36 19 lload 4 [local2]
37 21 aload_1 [param]
38 22 invokespecial levin.test.anonymous.MyApplication$1(levin.test.anonymous.MyApplication, long, java.lang.Integer) [30]
39 25 astore 6 [printer]
40 27 aload 6 [printer]
41 29 invokeinterface levin.test.anonymous.Printer.print() : void [33] [nargs: 1]
42 34 return
43 Line numbers:
44 [pc: 0, line: 11]
45 [pc: 4, line: 12]
46 [pc: 14, line: 13]
47 [pc: 27, line: 22]
48 [pc: 34, line: 23]
49 Local variable table:
50 [pc: 0, pc: 35] local: this index: 0 type: levin.test.anonymous.MyApplication
51 [pc: 0, pc: 35] local: param index: 1 type: java.lang.Integer
52 [pc: 4, pc: 35] local: local index: 2 type: long
53 [pc: 14, pc: 35] local: local2 index: 4 type: long
54 [pc: 27, pc: 35] local: printer index: 6 type: levin.test.anonymous.Printer
55
56 // Method descriptor #45 (Llevin/test/anonymous/MyApplication;)I
57 // Stack: 1, Locals: 1
58 static synthetic int access$0(levin.test.anonymous.MyApplication arg0);
59 0 aload_0 [arg0]
60 1 getfield levin.test.anonymous.MyApplication.field : int [12]
61 4 ireturn
62 Line numbers:
63 [pc: 0, line: 8]
64
65 Inner classes:
66 [inner class info: #28 levin/test/anonymous/MyApplication$1, outer class info: #0
67 inner name: #0, accessflags: 0 default]
68 }
從這兩段字節(jié)碼中可以看出,編譯器為我們的匿名類起了一個叫MyApplication$1的名字,它包含了三個final字段(這里synthetic修飾符是指這些字段是由編譯器生成的,它們并不存在于源代碼中):
MyApplication的應(yīng)用this$0
long值val$local2
Integer引用val$param
這些字段在構(gòu)造函數(shù)中賦值,而構(gòu)造函數(shù)則是在MyApplication.print()方法中調(diào)用。
由此,我們可以得出一個結(jié)論:Java對匿名內(nèi)部類的實(shí)現(xiàn)是通過編譯器來支持的,即通過編譯器幫我們產(chǎn)生一個匿名類的類名,將所有在匿名類中用到的局部變量和參數(shù)做為內(nèi)部類的final字段,同是內(nèi)部類還會引用外部類的實(shí)例。其實(shí)這里少了local的變量,這是因?yàn)?/span>local是編譯器常量,編譯器對它做了替換的優(yōu)化。
其實(shí)Java中很多語法都是通過編譯器來支持的,而在虛擬機(jī)/字節(jié)碼上并沒有什么區(qū)別,比如這里的final關(guān)鍵字,其實(shí)細(xì)心的人會發(fā)現(xiàn)在字節(jié)碼中,param參數(shù)并沒有final修飾,而final本身的很多實(shí)現(xiàn)就是由編譯器支持的。類似的還有Java中得泛型和逆變、協(xié)變等。這是題外話。
有了這個基礎(chǔ)后,我們就可以來分析為什么有些要用final修飾,有些卻不用的問題。
首先我們來分析local2變量,在”匿名類”中,它是通過構(gòu)造函數(shù)傳入到”匿名類”字段中的,因?yàn)樗腔绢愋停蚨趬蛑瘮?shù)中賦值時(撇開對函數(shù)參數(shù)傳遞不同虛擬機(jī)的不同實(shí)現(xiàn)而產(chǎn)生的不同效果),它事實(shí)上只是值的拷貝;因而加入我們可以在”匿名類”中得print()方法中對它賦值,那么這個賦值對外部類中得local2變量不會有影響,而程序員在讀代碼中,是從上往下讀的,所以很容易誤認(rèn)為這段代碼賦值會對外部類中得local2變量本身產(chǎn)生影響,何況在源碼中他們的名字都是一樣的,所以我認(rèn)為了避免這種confuse導(dǎo)致的一些問題,Java設(shè)計者才設(shè)計出了這樣的語法。
對引用類型,其實(shí)也是一樣的,因?yàn)橐玫膫鬟f事實(shí)上也只是傳遞引用的數(shù)值(簡單的可以理解成為地址),因而對param,如果可以在”匿名類”中賦值,也不會在外部類的print()后續(xù)方法產(chǎn)生影響。雖然這樣,我們還是可以在內(nèi)部類中改變引用內(nèi)部的值的,如果引用類型不是只讀類型的話;在這里Integer是只讀類型,因而我們沒法這樣做。(如果學(xué)過C++的童鞋可以想想常量指針和指針常量的區(qū)別)。
現(xiàn)在還剩下最后一個問題:為什么引用外部類的字段卻是可以不用final修飾的呢?細(xì)心的童鞋可能也已經(jīng)發(fā)現(xiàn)答案了,因?yàn)閮?nèi)部類保存了外部類的引用,因而內(nèi)部類中對任何字段的修改都回真實(shí)的反應(yīng)到外部類實(shí)例本身上,所以不需要用final來修飾它。
這個問題基本上就分析到這里了,不知道我有沒有表達(dá)清楚了。
加點(diǎn)題外話吧。
首先是,對這里的字節(jié)碼,其實(shí)還有一點(diǎn)可以借鑒的地方,就是內(nèi)部類在使用外部類的字段時不是直接取值,而是通過編譯器在外部類中生成的靜態(tài)的access$0()方法來取值,我的理解,這里Java設(shè)計者想盡量避免其他類直接訪問一個類的數(shù)據(jù)成員,同時生成的access$0()方法還可以被其他類所使用,這遵循了面向?qū)ο笤O(shè)計中的兩個重要原則:封裝和復(fù)用。
另外,對這個問題也讓我意識到了即使是語言語法層面上的設(shè)計都是有原因可循的,我們要善于多問一些為什么,理解這些設(shè)計的原因和局限,記得曾聽到過一句話:知道一門技術(shù)的局限,我們才能很好的理解這門技術(shù)可以用來做什么。也只有這樣我們才能不斷的提高自己。在解決了這個問題后,我突然冒出了一句說Java這樣設(shè)計也是合理的。是啊,語法其實(shí)就一幫人創(chuàng)建的一種解決某些問題的方案,當(dāng)然有合理和不合理之分,我們其實(shí)不用對它視若神圣。
之前有進(jìn)過某著名高校的研究生群,即使在那里,碼農(nóng)論也是甚囂塵上,其實(shí)碼農(nóng)不碼農(nóng)并不是因?yàn)槌绦騿T這個職位引起的,而是個人引起的,我們要不斷理解代碼內(nèi)部的本質(zhì)才能避免一直做碼農(nóng)的命運(yùn)那。個人愚見而已,呵呵。