7.4 使用Java类

你知道,Clojure并不是一种面向对象的语言。Clojure开发小组给Clojure添加了多项功能,以确保它能够使用Java类库和其他JVM库中的类以及创建类。

Clojure支持两种创建类实例的方式。一是使用new

  1. (def x (new java.util.ArrayList () ))

这里定义了一个指向ArrayList实例的变量。通过传递一个空的ArrayList ()方法,没有给构造函数传递任何参数。另一种创建实例的方式是在类名后面加上一个句点:

  1. (def x (java.util.ArrayList. () ))

注意,在ArrayList后面加上了句点。从功能上说,这两种方式没有什么不同。

7.4 使用Java类 - 图1 在Clojure程序中使用可变集合前一定要三思。由于Clojure是一种函数式编程语言,通常应尽可能使用Clojure的不可变集合。

要对对象实例调用方法,可在方法名前面加上句点,再指定对象实例以及方法的参数:

  1. (.add x 10)

在这里,方法add返回true。要查看变量x的内容,只需在REPL中输入x并按回车,REPL将打印[10]

要对同一个实例调用多个方法,一种便利的方式是使用doto宏:

  1. (doto x (.add 20) (.add 30) (.add 40))

doto宏返回指定的对象实例,因此上述代码打印[10, 20, 30 40]

要访问类属性(静态字段),可使用全限定类名、斜杠和成员名:

  1. (java.lang.Integer/MAX_VALUE)

这将返回2147483647

再举一个例子——使用java.lang.System类的静态字段out打印一条消息:

  1. (.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对象,因此上述表达式的结果为前面显示的对象。

使用deftypedefrecord创建简单的Java类

Clojure提供了deftypedefrecord宏,你可使用它们来动态地生成包含构造函数和字段的Java类,而这些Java类可用来定义存储简单数据的类型。下面是一个deftype宏的使用示例:

  1. (deftype Position2D [x y])

这里定义了一个名为Position2D的类,其中包含两个字段——xy。要根据这个类实例化一个对象,可像下面这样做:

  1. (def position (Position2D. 5 10))

这创建了一个名为position的变量,它指向的Position2D对象的字段xy的值分别为510。请注意,类名Position2D后面的句点是必不可少的,这种语法在前面讨论过。

使用这两个宏定义类时,如果指定了一个或多个Java接口,也可给类添加方法,但仅限于接口中定义的方法。假设我们要实现一种可关闭的资源,即它实现了Java类库中的java.io.Closeable接口:

  1. (deftype SomeCloseableResource []
  2. java.io.Closeable
  3. (close
  4. [this]
  5. (println "Closing resource...")))

java.io.Closeable接口只定义了一个方法——close()。这个方法不接受任何参数,但在Clojure中,必须定义包含当前实例引用的变量(在Java中,这个变量名为this)。

7.4 使用Java类 - 图2 在实例方法中,Java自动定义变量this,但在Clojure中,你必须手动为每个方法定义这个变量。

下面的示例演示了如何使用方法close()

  1. (def resource (SomeCloseableResource.))
  2. (.close resource)

这将向控制台打印文本Closing resource…

defrecord宏在语法方面与deftype相同,但存在如下重要的不同之处。

  • 使用defrecord定义的类包含一个永久性映射,这引入了可扩展的字段——可在这个映射中添加定义类时没有定义的键。
  • 使用deftype定义的类可包含可修改的字段,而在使用defrecord定义的类中,字段都是不可修改的。

下面是一个使用defrecord定义的简单类;这个示例表明,使用defrecord定义的类实现了一个类似于映射的接口:

  1. (defrecord Record [:field1 :field2])
  2. (def rec (Record. "value1" "value2"))
  3. (contains? rec :field2)

这个表达式的结果为true