記得vamcily 曾問我為什麼獲取數組的長度用length(成員變量的形式)而獲取String的長度用length()(成員方法的形式)?
我當時一聽覺得問得很有道理做同樣一件事情為什麼采用兩種風格迥異的風格呢?況且Java中的數組其實是完備(full fledged)的對象直接暴露成員變量可能不是一種很OO的風格那麼設計Java的那幫天才為什麼這麼做呢?
帶著這個疑問我查閱了一些資料主要是關於JVM是如何處理數組的
數組對象的類是什麼?
既然數組都是對象那麼數組的類究竟是什麼呢?當然不是javautilArrays啦!我們以int一維數組為例看看究竟
public class Main {
public static void main(String args[]){
int a[] = new int[]; Class clazz = agetClass();
Systemoutprintln(clazzgetName());
}
}
在SUN JDK 上運行上述代碼輸出為
[I
看起來數組的類很奇怪非但不屬於任何包而且名稱還不是合法的標識符(identifier)具體的命名規則[]可以參見 javalangClassgetName()的javadoc簡單的說數組的類名由若干個[和數組元素類型的內部名稱組成[的數目 代表了數組的維度
具有相同類型元素和相同維度的數組屬於同一個類如果兩個數組的元素類型相同但維度不同那麼它們也屬於不同的類如果兩個數組的元素類型 和維度均相同但長度不同那麼它們還是屬於同一個類
數組的類有哪些成員呢?
既然我們知道了數組的類名是什麼那麼就去看看數組的類究竟是什麼樣的吧?有哪些成員變量?有哪些成員方法?length這個成員變量在哪?是 不是沒有length()這個成員方法?
找來找去在JDK的代碼中沒有找打[I這個類想想也對[I都不是一個合法的標識符肯定不會出現public class [I {}這樣的Java代碼我們暫且不管[I類是誰聲明的怎麼聲明的先用反射機制一探究竟吧
public class Main {
public static void main(String[] args) {
int a[] = new int[]; Class clazz = agetClass();
Systemoutprintln(clazzgetDeclaredFields()length);
Systemoutprintln(clazzgetDeclaredMethods()length);
Systemoutprintln(clazzgetDeclaredConstructors()length);
Systemoutprintln(clazzgetDeclaredAnnotations()length);
Systemoutprintln(clazzgetDeclaredClasses()length);
Systemoutprintln(clazzgetSuperclass());
}
}
在SUN JDK 上運行上述代碼輸出為
class javalangObject 可見[I這個類是javalangObject的直接子類自身沒有聲明任何成員變量成員方法構造函數和Annotation可以說[I就 是個空類我們立馬可以想到一個問題怎麼連length這個成員變量都沒有呢?如果真的沒有編譯器怎麼不報語法錯呢?想必編譯器對 Arraylength進行了特殊處理哇!
數組的類在哪裡聲明的?
先不管為什麼沒有length成員變量我們先搞清楚[I這個類是哪裡聲明的吧既然[I都不是合法的標識符那麼這個類肯定在Java代碼中 顯式聲明的想來想去只能是JVM自己在運行時生成的了JVM生成類還是一件很容易的事情甚至無需生成字節碼直接在方法區中創建類型數據就差不 多完工了
還沒有實力去看JVM的源代碼於是翻了翻The JavaTM Virtual Machine Specification Second Edition果然得到了驗證相關內容參考 Creating Array Classes
規范的描述很嚴謹還摻雜了定義類加載器和初始化類加載器的內容先不管這些簡單概括一下
類加載器先看看數組類是否已經被創建了如果沒有那就說明需要創建數組類如果有那就無需創建了
如果數組元素是引用類型那麼類加載器首先去加載數組元素的類
JVM根據元素類型和維度創建相應的數組類
呵呵果然是JVM這家伙自個偷偷創建了[I類JVM不把數組類放到任何包中也不給他們起個合法的標識符名稱估計是為了避免和JDK第 三方及用戶自定義的類發生沖突吧
再想想JVM也必須動態生成數組類因為Java數組類的數量與元素類型維度(最多)有關相當相當多了是沒法預先聲明好的
居然沒有length這個成員變量!
我們已經發現偷懶的JVM沒有為數組類生成length這個成員變量那麼Arraylength這樣的語法如何通過編譯如何執行的呢?
讓我們看看字節碼吧!編寫一段最簡單的代碼使用jclasslib查看字節碼
public class Main {
public static void main(String[] args) {
int a[] = new int[]; int i = alength;
}
}
使用SUN JDK 編譯上述代碼並使用jclasslib打開Mainclass文件得到main方法的字節碼
iconst_ //將int型常量壓入操作數棧 newarray (int) //將彈出操作數棧作為長度創建一個元素類型為int 維度為的數組並將數組的引用壓入操作數棧 astore_ //將數組的引用從操作數棧中彈出保存在索引為的局部變量(即a)中 aload_ //將索引為的局部變量(即a)壓入操作數棧 arraylength //從操作數棧彈出數組引用(即a)並獲取其長度(JVM負責實現如何獲取)並將長度壓入操作數棧 istore_ //將數組長度從操作數棧彈出保存在索引為的局部變量(即i)中 return //main方法返回 可見在這段字節碼中根本就沒有看見length這個成員變量獲取數組長度是由一條特定的指令arraylength實現(怎麼實現就不管了JVM 總有辦法)編譯器對Arraylength這樣的語法做了特殊處理直接編譯成了arraylength指令另外JVM創建數組類應該就是由 newarray這條指令觸發的了
很自然地想到編譯器也可以對Arraylength()這樣的語法做特殊處理直接編譯成arraylength指令這樣的話我們就可 以使用方法調用的風格獲取數組的長度了這樣看起來貌似也更加OO一點那為什麼不使用Arraylength()的語法呢?也許是開發Java的那幫 天才對length有所偏愛或者拋硬幣拍腦袋隨便決定的吧 形式不重要重要的是我們明白了背後的機理
Array in Java
最後對Java中純對象的數組發表點感想吧
相比C/C++中的數組Java數組在安全性要好很多C/C++常遇到的緩存區溢出或數組訪問越界的問題在Java中不再存在因為 Java使用特定的指令訪問數組的元素這些指令都會對數組的長度進行檢查如果發現越界就會拋出 javalangArrayIndexOutOfBoundsException
Java數組元素的靈活性比較大一個數組的元素本身也可以是數組只要所有元素的數組類型相同即可我們知道數組的類型和長度無關因此元素 可以是長度不同的數組這樣Java的多維數組就不一定是規規矩矩的矩陣了可以千變萬化
From:http://tw.wingwit.com/Article/program/Java/hx/201311/25592.html