記得vamcily 曾問(wèn)我:“為什么獲取數(shù)組的長(zhǎng)度用.length(成員變量的形式),而獲取String的長(zhǎng)度用.length()(成員方法的形式)?”
我當(dāng)時(shí)一聽(tīng),覺(jué)得問(wèn)得很有道理。做同樣一件事情,為什么采用兩種風(fēng)格迥異的風(fēng)格呢?況且,Java中的數(shù)組其實(shí)是完備(full-fledged)的對(duì)象,直接暴露成員變量,可能不是一種很OO的風(fēng)格。那么,設(shè)計(jì)Java的那幫天才為什么這么做呢?
帶著這個(gè)疑問(wèn),我查閱了一些資料,主要是關(guān)于“JVM是如何處理數(shù)組”的。
數(shù)組對(duì)象的類是什么?
既然數(shù)組都是對(duì)象,那么數(shù)組的類究竟是什么呢?當(dāng)然不是java.util.Arrays啦!我們以int一維數(shù)組為例,看看究竟。
- public class Main {
- public static void main(String args[]){
- int a[] = new int[10]; Class clazz = a.getClass();
- System.out.println(clazz.getName());
- }
- }
在SUN JDK 1.6上運(yùn)行上述代碼,輸出為:
[I
看起來(lái)數(shù)組的類很奇怪,非但不屬于任何包,而且名稱還不是合法的標(biāo)識(shí)符(identifier)。具體的命名規(guī)則[1]可以參見(jiàn)java.lang.Class.getName()的javadoc。簡(jiǎn)單的說(shuō),數(shù)組的類名由若干個(gè)'['和數(shù)組元素類型的內(nèi)部名稱組成,'['的數(shù)目代表了數(shù)組的維度。
具有相同類型元素和相同維度的數(shù)組,屬于同一個(gè)類。如果兩個(gè)數(shù)組的元素類型相同,但維度不同,那么它們也屬于不同的類。如果兩個(gè)數(shù)組的元素類型和維度均相同,但長(zhǎng)度不同,那么它們還是屬于同一個(gè)類。
數(shù)組的類有哪些成員呢?
既然我們知道了數(shù)組的類名是什么,那么就去看看數(shù)組的類究竟是什么樣的吧?有哪些成員變量?有哪些成員方法?length這個(gè)成員變量在哪?是不是沒(méi)有l(wèi)ength()這個(gè)成員方法?
找來(lái)找去,在JDK的代碼中沒(méi)有找打'[I'這個(gè)類。想想也對(duì),'[I'都不是一個(gè)合法的標(biāo)識(shí)符,肯定不會(huì)出現(xiàn)public class [I {...}這樣的Java代碼。我們暫且不管[I類是誰(shuí)聲明的,怎么聲明的,先用反射機(jī)制一探究竟吧。
- public class Main {
- public static void main(String[] args) {
- int a[] = new int[10];
- Class clazz = a.getClass();
- System.out.println(clazz.getDeclaredFields().length);
- System.out.println(clazz.getDeclaredMethods().length);
- System.out.println(clazz.getDeclaredConstructors().length);
- System.out.println(clazz.getDeclaredAnnotations().length);
- System.out.println(clazz.getDeclaredClasses().length);
- System.out.println(clazz.getSuperclass());
- }
- }
在SUN JDK 1.6上運(yùn)行上述代碼,輸出為:
- 0
- 0
- 0
- 0
- 0
- class java.lang.Object
可見(jiàn),[I這個(gè)類是java.lang.Object的直接子類,自身沒(méi)有聲明任何成員變量、成員方法、構(gòu)造函數(shù)和Annotation,可以說(shuō),[I就是個(gè)空類。我們立馬可以想到一個(gè)問(wèn)題:怎么連length這個(gè)成員變量都沒(méi)有呢?如果真的沒(méi)有,編譯器怎么不報(bào)語(yǔ)法錯(cuò)呢?想必編譯器對(duì)Array.length進(jìn)行了特殊處理哇!
數(shù)組的類在哪里聲明的?
先不管為什么沒(méi)有l(wèi)ength成員變量,我們先搞清楚[I這個(gè)類是哪里聲明的吧。既然[I都不是合法的標(biāo)識(shí)符,那么這個(gè)類肯定在Java代碼中顯式聲明的。想來(lái)想去,只能是JVM自己在運(yùn)行時(shí)生成的了。JVM生成類還是一件很容易的事情,甚至無(wú)需生成字節(jié)碼,直接在方法區(qū)中創(chuàng)建類型數(shù)據(jù),就差不多完工了。
還沒(méi)有實(shí)力去看JVM的源代碼,于是翻了翻The JavaTM Virtual Machine Specification Second Edition,果然得到了驗(yàn)證,相關(guān)內(nèi)容參考5.3.3 Creating Array Classes。
規(guī)范的描述很嚴(yán)謹(jǐn),還摻雜了定義類加載器和初始化類加載器的內(nèi)容。先不管這些,簡(jiǎn)單概括一下:
類加載器先看看數(shù)組類是否已經(jīng)被創(chuàng)建了。如果沒(méi)有,那就說(shuō)明需要?jiǎng)?chuàng)建數(shù)組類;如果有,那就無(wú)需創(chuàng)建了。
如果數(shù)組元素是引用類型,那么類加載器首先去加載數(shù)組元素的類。
JVM根據(jù)元素類型和維度,創(chuàng)建相應(yīng)的數(shù)組類。
呵呵,果然是JVM這家伙自個(gè)偷偷創(chuàng)建了[I類。JVM不把數(shù)組類放到任何包中,也不給他們起個(gè)合法的標(biāo)識(shí)符名稱,估計(jì)是為了避免和JDK、第三方及用戶自定義的類發(fā)生沖突吧。
再想想,JVM也必須動(dòng)態(tài)生成數(shù)組類,因?yàn)镴ava數(shù)組類的數(shù)量與元素類型、維度(最多255)有關(guān),相當(dāng)相當(dāng)多了,是沒(méi)法預(yù)先聲明好的。
居然沒(méi)有l(wèi)ength這個(gè)成員變量!
我們已經(jīng)發(fā)現(xiàn),偷懶的JVM沒(méi)有為數(shù)組類生成length這個(gè)成員變量,那么Array.length這樣的語(yǔ)法如何通過(guò)編譯,如何執(zhí)行的呢?
讓我們看看字節(jié)碼吧!編寫一段最簡(jiǎn)單的代碼,使用jclasslib查看字節(jié)碼。
- public class Main {
- public static void main(String[] args)
- { int a[] = new int[2]; int i = a.length;
- }
- }
使用SUN JDK 1.6編譯上述代碼,并使用jclasslib打開(kāi)Main.class文件,得到main方法的字節(jié)碼:
- 0 iconst_2 //將int型常量2壓入操作數(shù)棧
- 1 newarray 10 (int) //將2彈出操作數(shù)棧,作為長(zhǎng)度,創(chuàng)建一個(gè)元素類型為int, 維度為1的數(shù)組,并將數(shù)組的引用壓入操作數(shù)棧
- 3 astore_1 //將數(shù)組的引用從操作數(shù)棧中彈出,保存在索引為1的局部變量(即a)中
- 4 aload_1 //將索引為1的局部變量(即a)壓入操作數(shù)棧
- 5 arraylength //從操作數(shù)棧彈出數(shù)組引用(即a),并獲取其長(zhǎng)度(JVM負(fù)責(zé)實(shí)現(xiàn)如何獲取),并將長(zhǎng)度壓入操作數(shù)棧
- 6 istore_2 //將數(shù)組長(zhǎng)度從操作數(shù)棧彈出,保存在索引為2的局部變量(即i)中
- 7 return //main方法返回
可見(jiàn),在這段字節(jié)碼中,根本就沒(méi)有看見(jiàn)length這個(gè)成員變量,獲取數(shù)組長(zhǎng)度是由一條特定的指令arraylength實(shí)現(xiàn)(怎么實(shí)現(xiàn)就不管了,JVM總有辦法)。編譯器對(duì)Array.length這樣的語(yǔ)法做了特殊處理,直接編譯成了arraylength指令。另外,JVM創(chuàng)建數(shù)組類,應(yīng)該就是由newarray這條指令觸發(fā)的了。
很自然地想到,編譯器也可以對(duì)Array.length()這樣的語(yǔ)法做特殊處理,直接編譯成arraylength指令。這樣的話,我們就可以使用方法調(diào)用的風(fēng)格獲取數(shù)組的長(zhǎng)度了,這樣看起來(lái)貌似也更加OO一點(diǎn)。那為什么不使用Array.length()的語(yǔ)法呢?也許是開(kāi)發(fā)Java的那幫天才對(duì).length有所偏愛(ài),或者拋硬幣拍腦袋隨便決定的吧。 形式不重要,重要的是我們明白了背后的機(jī)理。
Array in Java
最后,對(duì)Java中純對(duì)象的數(shù)組發(fā)表點(diǎn)感想吧。
相比C/C++中的數(shù)組,Java數(shù)組在安全性要好很多。C/C++常遇到的緩存區(qū)溢出或數(shù)組訪問(wèn)越界的問(wèn)題,在Java中不再存在。因?yàn)镴ava使用特定的指令訪問(wèn)數(shù)組的元素,這些指令都會(huì)對(duì)數(shù)組的長(zhǎng)度進(jìn)行檢查。如果發(fā)現(xiàn)越界,就會(huì)拋出java.lang.ArrayIndexOutOfBoundsException。
Java數(shù)組元素的靈活性比較大。一個(gè)數(shù)組的元素本身也可以是數(shù)組,只要所有元素的數(shù)組類型相同即可。我們知道數(shù)組的類型和長(zhǎng)度無(wú)關(guān),因此元素可以是長(zhǎng)度不同的數(shù)組。這樣,Java的多維數(shù)組就不一定是規(guī)規(guī)矩矩的矩陣了,可以千變?nèi)f化。
聯(lián)系客服