iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 20
0
Software Development

擁抱 Clojure系列 第 20

[第 20 天] 擁抱 Clojure:與 Java 共舞(二)

與 Java 共舞(二)

從 Clojure 呼叫 Java

匿名類型

在前一章提到過的 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 代表到時建立的執行個體;使用 reifydeftypedefrecord 則需要明確指定 this 參數。

具名類型

reifyproxy 讓我們在動態時期建立匿名類型,然而也會有需要提供靜態具名類型的時候,尤其是 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 的方法中,有可能會遇到參數需要傳遞物件的陣列,或是該方法爲不定引數,此時就需要一些與 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 陣列使用 agetaset 兩個函式:

(def my-array (into-array ["a" "b" "c" "d"]))
(aget my-array 2)
;; => "c"
(aset my-array 2 "C")
;; => "C"

參數分別爲陣列的索引值,與打算設定的新值。

(未完待續)


上一篇
[第 19 天] 擁抱 Clojure:與 Java 共舞(一)
下一篇
[第 21 天] 擁抱 Clojure:與 Java 共舞(三)
系列文
擁抱 Clojure30

尚未有邦友留言

立即登入留言