Java 互操作

Clojure程序可以使用所有的java类以及接口。和在java里面一样 java.lang 这个包里面的类是默认导入的。你可以手动的用 import 函数来导入其它包的类。看例子:

(import
  '(java.util Calendar GregorianCalendar)
  '(javax.swing JFrame JLabel))

同时也可以看下宏ns下面的 [:import](http://xumingming.sinaapp.com/302/clojure-functional-programming-for-the-jvm-clojure-tutorial/#nsMacro) 指令, 我们会在后面介绍的。

有两种方式可以访问类里面的常量的:

(. java.util.Calendar APRIL) ; -> 3
(. Calendar APRIL) ; works if the Calendar class was imported
java.util.Calendar/APRIL
Calendar/APRIL ; works if the Calendar class was imported

在Clojure代码里面调用java的方法是很简单的。因此很多java里面已经实现的功能Clojure就没有实现自己的了。比如, Clojure里面没有提供函数来计算一个数的绝对值,因为可以用 java.lang.Math 里面的abs方法。而另一方面,比如这个类里面还提供了一个 max 方法来计算两个数里面比较大的一个, 但是它只接受两个参数,因此Clojure里面自己提供了一个可以接受多个参数的max函数。

有两种方法可以调用java里面的静态方法:

(. Math pow 2 4) ; -> 16.0
(Math/pow 2 4)

同样也有两种方法来创建一个新的java的对象,看下面的例子。这里注意一下我们用 def 创建的对象bind到一个全局的binding。这个其实不是必须的。有好几种方式可以得到一个对象的引用比如把它加入一个集合或者把它传入一个函数。

(import '(java.util Calendar GregorianCalendar))
(def calendar (new GregorianCalendar 2008 Calendar/APRIL 16)) ; April 16, 2008
(def calendar (GregorianCalendar. 2008 Calendar/APRIL 16))

同样也有两种方法可以调用java对象的方法:

(. calendar add Calendar/MONTH 2)
(. calendar get Calendar/MONTH) ; -> 5
(.add calendar Calendar/MONTH 2)
(.get calendar Calendar/MONTH) ; -> 7

一般来说我们比较推荐使用下面那种用法(.add, .get), 上面那种用法在定义宏的时候用得比较多, 这个等到我们讲到宏的时候再做详细介绍。

方法调用可以用 .. 宏串起来:

(. (. calendar getTimeZone) getDisplayName) ; long way
(.. calendar getTimeZone getDisplayName) ; -> "Central Standard Time"

还一个宏: .?.clojure.contrib.core 名字空间里面, 它和上面..这个宏的区别是,在调用的过程中如果有一个返回结果是nil, 它就不再继续调用了,可以防止出现 NullPointerException 异常。

doto 函数可以用来调用一个对象上的多个方法。它返回它的第一个参数, 也就是所要调用方法的对象。这对于初始化一个对象的对各属性是非常方便的。 (看下面”Namespaces“那一节的 JFrame GUI 对象的例子). 比如:

(doto calendar
  (.set Calendar/YEAR 1981)
  (.set Calendar/MONTH Calendar/AUGUST)
  (.set Calendar/DATE 1))
(def formatter (java.text.DateFormat/getDateInstance))
(.format formatter (.getTime calendar)) ; -> "Aug 1, 1981"

memfn 宏可以自动生成代码以使得java方法可以当成clojure里面的“一等公民”来对待。这个可以用来替代clojure里面的匿名方法。当用 memfn 来调用java里面那些需要参数的方法的时候, 你必须给每个参数指定一个名字,以让clojure知道你要调用的方法需要几个参数。这些名字到底是什么不重要,但是它们必须要是唯一的, 因为要用这些名字来生成Clojure代码的。下面的代码用了一个map方法来从第二个集合里面取beginIndex来作为参数调用第一个集合里面的字符串的substring方法。大家可以看一下用匿名函数和用memfn来直接调用java的方法的区别。

(println (map #(.substring %1 %2)
           ["Moe" "Larry" "Curly"] [1 2 3])) ; -> (oe rry ly)

(println (map (memfn substring beginIndex)
           ["Moe" "Larry" "Curly"] [1 2 3])) ; -> same