在前一章提到過的 reify
巨集,能夠用來繼承父類別,產生匿名類別:
(.listFiles (java.io.File. ".")
(reify java.io.FileFilter
(accept [this f]
(.isDirectory f))))
以上範例使用 java.io.File
其中的 listFiles
方法,它接受一個實作 FileFilter
的執行個體,透過此執行個體的 accept
方法來決定要保留哪些檔案。範例中使用 reify
實作了 FileFilter
中的 accept
方法。
除了 reify
之外,proxy
也提供產生匿名類別的功能,但是 reify
只能用來實作協議或是介面,無法用來擴充實際的型態;當你需要覆寫父類別的方法時,只能用 proxy
來達成。
proxy
接受的第一個參數是向量,其中包含了欲實作繼承的類別或介面的名稱,第二個參數爲內含建構式參數的向量,接下來則是覆寫的函式,大略像以下的形式:
(proxy [class-or-interface-name] [constructor-parameters]
(method-name-1 [method-parameters]
method-body)
(method-name-2 [method-parameters]
method-body)
由於 reify
以及 proxy
的函式爲閉包,可以訪問建立此閉包環境中的資訊,因此可以達成類似於執行個體屬性的功能:
(str (let [f "foo"]
(proxy [Object] []
(toString [] f))))
值得注意的是,使用 proxy
建立的匿名類別中的函式,不需要在參數中有明確指定 this
指向該執行個體,proxy
會隱式地建立 this
代表到時建立的執行個體;使用 reify
、deftype
、defrecord
則需要明確指定 this
參數。
reify
與 proxy
讓我們在動態時期建立匿名類型,然而也會有需要提供靜態具名類型的時候,尤其是 Clojure 端打算提供給 Java 端功能的時候。
產生具名類型的方式是透過 gen-class
巨集,它提供了許多修飾子來調整產生出來的類型相關屬性,除了指定類型名稱之外,其他都不是必要提供的。
gen-class
巨集會根據所給予的資訊產生對應的 Java 類別檔案 (.class),以下介紹 gen-class
以及一些修飾子:
(gen-class
:name tw.embracing-clojure.example ;; 類別名稱
:extends com.example.baseclass ;; 父類別名稱
:implements [com.example.IFace] ;; 欲實作的介面
:constructors {[String] [String]} ;; 建構式的返回值型態與參數型態
:init initialize ;; 指定當作建構式的函式
:state state ;; 指定類別中的公有最終屬性
:methods [[doSomething [Byte] String] ;; 類別的方法
[show [] String]]
)
(defn init [a b]
[[(str a b)] {ref {}}])
(defn doSomething [this b]
"do something")
(defn show [this]
"show")
:name
定義了欲產生的類別名稱,此處不可缺少。
:extends
欲擴充的父類別名稱。
:implements
如果有欲實作的介面,則寫於此處的向量之中。
:constructors
該類別建構式的返回值與參數的型態寫於此處。寫法爲以一個映射中包含向量爲索引鍵,以及內容值爲向量的每對資料,其中索引鍵向量中寫下返回值的型態,內容向量則寫下各參數的型態。
:init
指定用來當作建構式的函式名稱,該函式必須返回一個向量,該向量包含兩個元素,其中一個是傳給父類別建構式各參數值的向量,以及代表狀態的值,該狀態值爲原子 (Atom) 型態。
:state
在此處指定的名稱,將會在產生的類別中建立一個同名的執行個體屬性,爲不可變動 (final)。它的值必須要在 init
函式中指定。
:methods
此處指名了產生的類別中新增的方法,不需要將欲覆寫的父類別方法寫在這。
除了這裡介紹的修飾子之外,沒有提到的部分有興趣的讀者可以參考官方文件,或在 REPL 輸入 (doc gen-class)
即可取得詳細資訊。
之前的章節中提到過的 ns
巨集,除了可以建立命名空間之外,也具備產生具名類別的功能。其功能與提供的修飾子都跟 gen-class
一樣:
(ns com.example.clojure
(:gen-class
:methods [[show [] void]]))
在 ns
巨集裡的 gen-class
若沒有指定 :name
,則使用該命名空間當作類別名稱。
在 Java 的方法中,有可能會遇到參數需要傳遞物件的陣列,或是該方法爲不定引數,此時就需要一些與 Java 陣列有關的協作函式,可以創建或是修改陣列。
你可以使用 into-array
將序列轉成陣列:
(into-array [\x \y \z])
;; => #object["[Ljava.lang.Character;" 0x1a8aca92 "[Ljava.lang.Character;@1a8aca92"]
也可以使用 make-array
函式,明確指定該陣列的型態與容量:
(make-array Integer/TYPE 10)
;; => #object["[I" 0x44bcf69d "[I@44bcf69d"]
或是可以使用下列明確寫明型態的陣列創建函式:
boolean-array
byte-array
char-array
double-array
float-array
int-array
long-array
object-array
short-array
(long-array 10)
;; => #object["[J" 0x44e25f4d "[J@44e25f4d"]
(char-array 5)
;; => #object["[C" 0x176b9225 "[C@176b9225"]
(float-array 15)
;; => #object["[F" 0x19b993c9 "[F@19b993c9"]
#object["[J" 0x44e25f4d "[J@44e25f4d"]
代表是個原生 (Primitive) 的 long 型態陣列、#object["[C" 0x176b9225 "[C@176b9225"]
則是原生的 char 型態陣列,而 #object["[F" 0x19b993c9 "[F@19b993c9"]
則是原生的 float 型態陣列。
存取 Java 陣列使用 aget
與 aset
兩個函式:
(def my-array (into-array ["a" "b" "c" "d"]))
(aget my-array 2)
;; => "c"
(aset my-array 2 "C")
;; => "C"
參數分別爲陣列的索引值,與打算設定的新值。
(未完待續)