1. 程式人生 > >轉貼:關於java陣列的深度思考

轉貼:關於java陣列的深度思考

  剛剛開始接觸java陣列的人都會聽到一句類似的話:java是純面向物件的語言,他的陣列也是一個物件。於是乎,筆者就按照一個物件的方式來使用陣列,心安理得。直到我接觸到C的陣列後,才發現將陣列作為一個類來使用在實現上是多麼的“不自然”。

  
  首先我們看一下表面現象,陣列建立的時候採用的是如下語句:

  MyClass[] arr = new MyClass[9];

  而普通類採用的是如下語句:

  MyClass obj = new MyClass();

  就是說,建立陣列的時候不使用小括號傳參。使得陣列和普通類看起來就有很多不同,因為小括號裡的引數是傳遞給構造方法的,進而讓人感覺陣列類是沒有構造方法的。

  
再往深了想,還有很多讓人感覺不自然的東西。可以肯定的是,java確實將陣列作為了一個類來處理。還是用上面的例子說明:

  可以通過以下方法得到MyClass[]的Class例項:arr.getClass()或MyClass[].class。這樣,我就可以向陣列類裡面“窺探”了。

  
  Class clazz = MyClass[].class;
  System.out.println(clazz.getConstructors().length);

  
  打印出來的結果是0;證明陣列類確實沒有構造方法。

  
  如果強行執行clazz.newInstance();就會得到下面的錯誤。

  java.lang.InstantiationException: [Larraytest.MyClass;

  證明陣列類不能夠通過普通的反射方式來建立一個例項。

  
  再看看陣列類的“廬山真面目”:

  System.out.println(clazz);

  輸出是:

  [Larraytest.MyClass

  對Java Class檔案結構稍有了結就知道,這個字串的意思就是一個元素型別為arraytest.MyClass的一維陣列。也就是說,陣列型別不是和普通類一樣,以一個全限定路徑名 類名來作為自己的唯一標示的,而是以[ 一個或者多個L 陣列元素類全限定路徑 類來最為唯一標示的。這個()也是陣列和普通類的區別。而這個區別似乎在某種程度上說明陣列和普通java類在實現上有很大區別。因為java虛擬機器(java指令集)在處理陣列類和普通類的時候,肯定會做出區分。筆者猜想,可能會有專門的java虛擬機器指令來處理陣列。

  既然我們可以得到陣列的Class類例項,就說明肯定需要呼叫ClassLoader的 defineClass(不一定非要是loadClass方法)方法,來構造一個Class例項。java虛擬機器規範規定,任何一個可以被載入的類,如果其類檔案儲存在檔案系統上,那麼一個*.class檔案只能儲存一個類資訊,也就是說,陣列類的資訊不可能以類檔案的形式儲存在本地磁碟上(否則任意一個類都要配有255個數組類了.....),既然這樣,那就說明java虛擬機器肯定內建了一塊用來宣告陣列類的資料(不管是幾級陣列)。這是符合java虛擬機器規範的,規範規定class類資料可以來自任意介質,包括本地磁碟、網路、資料庫、記憶體等等。

  分析到這裡,我基本上可以肯定:java對陣列物件化的操作的支援是指令級的,也就是說java虛擬機器有專門針對陣列的指令。陣列的Class類例項是java虛擬機器動態建立動態載入的,其結構與普通java類的Class例項有一些不同。

  JDK API中有一個java.lang.reflect.Array類,這個類提供了很多方法(絕大多數是native方法,這在另一個方面證明了java對陣列的支援是專用指令支援的,否則用本地方法幹嘛^_^),用來彌補我們對陣列操作的侷限性。

  下面這句話用來建立一個一維的、長度為10的、型別為arraytest.MyClass的陣列:

  arraytest.MyClass[] arr = (arraytest.MyClass[]) Array.newInstance(arraytest.MyClass, 10);

  下面這句話用來建立一個二維的、3乘5的、型別為arraytest.MyClass的陣列:

  int[] arrModel = new int[]{3,5};
Object arrObj = Array.newInstance(Sub.class, arrModel);

  當然你可以用一個數組的引用指向上面的二維陣列,這裡我們用一個Object的引用指向他。
使用的時候,我們也是可以利用Array類提供的方法來實現:

  System.out.println(Array.getLength(arrObj);//第一維長度為3
  System.out.println(Array.getLength(Array.get(arrObj, 2)));//第二維長度為5,這裡如果寫3,就會得到你意想之中的java.lang.ArrayIndexOutOfBoundsException

  列印結果是如我們所想的:

  3
  5

  對於陣列的Class類例項,還有一些奇怪的現象:

  在執行程式碼 java.lang.reflect.Field fieldarr = clazz.getField("length");的時候,會丟擲異常:java.lang.NoSuchFieldException: length,這似乎在說陣列類沒有length這個域,而這個域其實是我們用的最多的一個(也就是說這個域是肯定存在的)。筆者認為關於陣列的Class類例項、陣列的實現等,還有很多“貓膩”在裡面。

  順便說一句,java陣列最多隻能是255維的。這個讓人看到了C的影子,嘿嘿。“Java把陣列當作一個java類來處理”說起來容易,用起來自然,但是細細想來,還是有很多不簡單的地方。