Clojure和Java
Clojure的代码是被编译为java字节码而执行的,因此Clojure和Java代码的集成度相当高,这也是很多人学习Clojure的原因,因为跟Lisp这样的语言相比,它不仅仅具备强大的表达能力,同时也能尽可能的利用现有的‘智力遗产’。
如何访问构造函数、方法和域
Clojure中提供了new关键字来生成一个对象 (new classname),生成的对象可以用def语句绑定到某个变量之上,绑定之后便可以用 . 运算符来访问该对象的方法:
(def rnd (new java.util.Random)) (. rnd nextInt) -791474443
对于方法调用的参数,则直接写在函数名后面就可以了:
(. rnd nextInt 10)
. 运算符不仅仅可以用在访问对象的方法,还可以用在访问对象的域、静态的或者非静态的都行。比如我们可以用(. Math PI)访问Math类下的静态变量PI
需要记住的是,clojure的new方法的参数必须是相对PATH的完整的类名,比如上面例子的Random类。不过有一个包中的类是例外:java.lang。
为了避免一直使用较长的类名字,我们通常会使用import方法: (import [& import-list]),这其中的import-list主要包括两个部分,前一部分表示包所在的路径,后一部分表示需要导入的类名字,比如:
(import '(java.util Random Locale) '(java.text MessageFormat))
这基本上就是Clojure调用Java的全部了。简单吧!
语法糖衣
大部分的java相关操作的语句都有一个简化版,比如new:
(new Random) == (Random .)
对于方位类中的静态元素,直接可以使用/,而不是.
(. Math Pi) == Math/PI
静态方法也可以用/来直接调用
(. System currentTimeMills) == (System/currentTimeMills)
我们甚至可以用更简单的方法来表述对象的函数调用或者域访问:
(. rnd nextInt) = (.nextInt rnd)
为了简化java中多个类不断调用,而如果写成这种前缀形式会显得比较丑陋。Clojure提供了 .. 宏
(.getLocation (.getCodeSource (.getProtectionDomain (.getClass '(1 2))))) == (.. '(1 2) getClass getProtectionDomain getCodeSource getLocation)
Java Collection
Clojure提供的Collection支持并发和事物内存机制,性能各方面也优于Java本身的Collection类,所以我们推荐使用Clojure的Collection。然而有一点就是Java的array无法用Clojure的collection来模拟,因此其提供了make-array来创建Java的array。
(make-array class length) (make-array class dim & more-dims)
上面两种语句都会返回一个java array对象,我们可以使用Clojure提供的seq方法来包裹java array,生成一个clojure的sequence对象。
可以使用下面的方法来获取clojure提供的array方法:
(find-doc "-array")
clojure也提供了一系列的低级操作来支持java array:
(aset java-array index value) (aset java-array index-dim1 index-dim2 ... value) (aget java-array index) (aget java-array index-dim1 index-dim2 ...) (alength java-array)
通常可以使用clojure的to-array方法直接将一个collection转换为java array:
(to-array ["Easier" "array" "creation"]) #<Object[] [Ljava.lang.Object;@1639f9e3>
into-array提供to-array类似的功能,但是却可以制定array中每一个元素的类型,而不是Object
(into-array String ["Easier", "array", "creation"])
如果没有类名,into-array也会根据参数才猜测之。
Cloure提供了amap函数来循环处理java array中的每一个元素
(amap a idx ret expr)
其表示,首先对传进的array - a 进行复制,并且把复制品绑定为ret,之后将对 a中的每一个元素执行expr,idx绑定的为当前元素的index。最后返回结果array。比如测试例子:
(def strings (into-array ["some" "strings" "here"])) #'user/strings (seq (amap strings idx _ (.toUpperCase (aget strings idx)))) ("SOME" "STRINGS" "HERE")
areduce是非常类似于amap的高阶函数,它的功能稍微复杂点:
(areduce a idx ret init expr)
Convenience Functions
Clojure作为一个函数式语言,是以函数为参数和返回值的,而Java并不具备该功能。因此Clojure提供了memfn宏用来将Java中的方法转化为Clojure中的方法,以便于作为参数或者返回值继续传递下去。
(map .toUpperCase ["a" "short" "message"]) java.lang.Exception:\ Unable to resolve symbol: .toUpperCase in this context (map (memfn toUpperCase) ["a" "short" "message"]) ("A" "SHORT" "MESSAGE")
更好的办法是利用匿名函数来包裹java函数:
(map #(.toUpperCase %) ["a" "short" "message"]) ("A" "SHORT" "MESSAGE")
Clojure还提供了一个检查给定对象是否属于某种类的判断语句instance?
(instance? String 10) false
Clojure为字符串提供了format函数来代替java本身的format函数
(format "%s ran %d miles today" "Stu" 8) "Stu ran 8 miles today"