7.4 使用Java类
你知道,Clojure并不是一种面向对象的语言。Clojure开发小组给Clojure添加了多项功能,以确保它能够使用Java类库和其他JVM库中的类以及创建类。
Clojure支持两种创建类实例的方式。一是使用new:
(def x (new java.util.ArrayList () ))
这里定义了一个指向ArrayList实例的变量。通过传递一个空的ArrayList ()方法,没有给构造函数传递任何参数。另一种创建实例的方式是在类名后面加上一个句点:
(def x (java.util.ArrayList. () ))
注意,在ArrayList后面加上了句点。从功能上说,这两种方式没有什么不同。
在Clojure程序中使用可变集合前一定要三思。由于Clojure是一种函数式编程语言,通常应尽可能使用Clojure的不可变集合。
要对对象实例调用方法,可在方法名前面加上句点,再指定对象实例以及方法的参数:
(.add x 10)
在这里,方法add返回true。要查看变量x的内容,只需在REPL中输入x并按回车,REPL将打印[10]。
要对同一个实例调用多个方法,一种便利的方式是使用doto宏:
(doto x (.add 20) (.add 30) (.add 40))
doto宏返回指定的对象实例,因此上述代码打印[10, 20, 30 40]。
要访问类属性(静态字段),可使用全限定类名、斜杠和成员名:
(java.lang.Integer/MAX_VALUE)
这将返回2147483647。
再举一个例子——使用java.lang.System类的静态字段out打印一条消息:
(.printf System/out "Hello %s!!n" (into-array String (list "world")))
输出类似于下面这样:#object[java.io.PrintStream 0x4ea5b703 "java.io.PrintStream@4ea5b703"](在你的计算机上,结果可能与此不同)。
上述代码片段演示了多种技巧。
- Clojure默认导入了
java.lang包,因此无需使用全限定类名java.lang.System。 PrintStream类的方法printf的第二个参数是一个字符串数组(String[]),因此我们使用Clojure函数into-array将只包含一个元素的列表转换为字符串数组。- 方法
printf返回当前PrintStream对象,因此上述表达式的结果为前面显示的对象。
使用deftype和defrecord创建简单的Java类
Clojure提供了deftype和defrecord宏,你可使用它们来动态地生成包含构造函数和字段的Java类,而这些Java类可用来定义存储简单数据的类型。下面是一个deftype宏的使用示例:
(deftype Position2D [x y])
这里定义了一个名为Position2D的类,其中包含两个字段——x和y。要根据这个类实例化一个对象,可像下面这样做:
(def position (Position2D. 5 10))
这创建了一个名为position的变量,它指向的Position2D对象的字段x和y的值分别为5和10。请注意,类名Position2D后面的句点是必不可少的,这种语法在前面讨论过。
使用这两个宏定义类时,如果指定了一个或多个Java接口,也可给类添加方法,但仅限于接口中定义的方法。假设我们要实现一种可关闭的资源,即它实现了Java类库中的java.io.Closeable接口:
(deftype SomeCloseableResource []java.io.Closeable(close[this](println "Closing resource...")))
java.io.Closeable接口只定义了一个方法——close()。这个方法不接受任何参数,但在Clojure中,必须定义包含当前实例引用的变量(在Java中,这个变量名为this)。
在实例方法中,Java自动定义变量
this,但在Clojure中,你必须手动为每个方法定义这个变量。
下面的示例演示了如何使用方法close():
(def resource (SomeCloseableResource.))(.close resource)
这将向控制台打印文本Closing resource…。
defrecord宏在语法方面与deftype相同,但存在如下重要的不同之处。
- 使用
defrecord定义的类包含一个永久性映射,这引入了可扩展的字段——可在这个映射中添加定义类时没有定义的键。 - 使用
deftype定义的类可包含可修改的字段,而在使用defrecord定义的类中,字段都是不可修改的。
下面是一个使用defrecord定义的简单类;这个示例表明,使用defrecord定义的类实现了一个类似于映射的接口:
(defrecord Record [:field1 :field2])(def rec (Record. "value1" "value2"))(contains? rec :field2)
这个表达式的结果为true。
在Clojure程序中使用可变集合前一定要三思。由于Clojure是一种函数式编程语言,通常应尽可能使用Clojure的不可变集合。