如何在 Clojure 中调用 .java 中的代码

最近我用 Clojure 写的 Hive UDF 中需要使用 IPIP 查询 IP 地理位置,有现成的 Java 版解析器:https://github.com/ilsanbao/17moncn/tree/master/java ,为了完成工作进度,把它转成 Clojure 成本是比较高的,所以需要把它直接引入到 Clojure 中。

因为 Clojure 代码和 Java 代码都最终会转成 JVM 解析的字节码,所以它们可以互相之间调用,这个过程 Leiningen 可以帮我们自动完成。为了让 Leiningen 找到 Java 写的代码,首先在 project.clj 中设置 Java 源码存放的路径:

:java-source-paths ["src/java"]

这里 Java 源码存放在 src/java 目录中的,src/java 中可以有子目录,Leiningen 会自动遍历。以下是我的目录结构:

src
├── java
│  └── main
│       └── IpLocation.java
└── xx
    └── core.clj

IpLocation.java 放在 src/java/main 目录中的,IpLocation.java 中定义了一个 IpLocation 类,现在我们在 Clojure 的 REPL 中测试看是否能直接调用:

$ lein repl

Leiningen 在加载之前会自动把 .java 编译 .class,这个过程会被 REPL 打印出来:

Compiling 1 source files to /tmp/ip/xx/target/classes

如果没有报错,可以顺利进入 REPL。然后把 IpLocation 这个类 import 进来,再新建一个 IpLocation 对象:

xx.core=> (import 'IpLocation)
IpLocation
xx.core=> (IpLocation.)
CompilerException java.lang.IllegalArgumentException: No matching ctor found for class IpLocation, compiling:(/tmp/form-init7230766958856873441.clj:1:1)

Oh,这里报错了:No matching ctor found for class IpLocation,需要把 IpLocation 类设置成 pulibc 才行。退出 REPL,修改 IpLocation.java 的源码,把它设置成 public:

public class IpLocation {
    ...
}

再运行 lein repl 进入交互式,并把 IpLocation 这个类 import 进来:

xx.core=> (import 'IpLocation)
IpLocation
xx.core=> (def ip-location (IpLocation.))
#'xx.core/ip-location
xx.core=> (.find ip-location "8.8.8.8")  ; find方法返回的是一个String数组
#<String[] [Ljava.lang.String;@2a476465>
xx.core=> (seq (.find ip-location "8.8.8.8")) ; 把find的返回结果转换成序列
("GOOGLE" "GOOGLE")