Programming Cljr – working with Java

Working with Java

In this chapter, you will see how Clojure access to Java is convenient, elegant, and fast:
• Calling Java is simple and direct.
  Clojure provides syntax extensions for accessing anything you could reach from Java code: classes, instances, constructors, methods, and fields.
  Although you will typically call Java code directly, you can also wrap Java APIs and use them in a more functional style.

• Clojure is fast, unlike many other dynamic languages on the JVM.
  You can use custom support for primitives and arrays, plus type hints, to cause Clojure’s compiler to generate the same code that a Java compiler would generate.

• Java code can call Clojure code, too. Clojure can generate Java classes on the fly. On a one-off basis, you can use proxy, or you can generate and save classes with gen-and-save-class.

• Clojure’s exception handling is easy to use. Better yet, explicit exception handling is rarely necessary.
  Clojure’s exception primitives are the same as Java’s. However, Clojure does not require you to deal with checked exceptions and makes it easy to clean up resources using the with-open idiom.

Calling Java

Accessing Constructors, Methods, and Fields

creating a Java object

(new java.util.Random)
java.util.Random@4f1ada
 
(def rnd (new java.util.Random))
 
(. rnd nextInt)
-791474443

import package

(import '(java.util Random Locale)
        '(java.text MessageFormat))

.. macro

The .. macro is great if the result of each operation is an input to the next.

(.getLocation (.getCodeSource (.getProtectionDomain (.getClass '(1 2)))))
 
(.. '(1 2) getClass getProtectionDomain getCodeSource getLocation) ;使用..宏的版本, 更清晰? 少写些括号?

Doto

Sometimes you don’t care about the results of method calls and simply want to make several calls on the same object.
The doto macro makes it easy to make several calls on the same object:

(doto class-or-inst & member-access-forms)

As the “do” in doto suggests, you can use doto to cause side effects in the mutable Java world. For example, use doto to set multiple system properties:

(doto (System/getProperties)
  (.setProperty "name" "Stuart")
  (.setProperty "favoriteColor" "blue"))

下面的表列出基本语法和响应的语法糖,

image

Using Java Collections

Clojure’s collections supplant the Java collections for most purposes.
Clojure’s collections are concurrency-safe, have good performance characteristics, and implement the appropriate Java collection interfaces.
So, you should generally prefer Clojure’s own collections when you are working in Clojure and even pass them back into Java when convenient.

暂时不清楚为什么要在clojure里面使用java collection, 用到再说...

Convenience Functions

memfn macro

Try passing .toUpperCase to map in order to upcase a vector of strings:

(map .toUpperCase ["a" "short" "message"])
java.lang.Exception:\
Unable to resolve symbol: .toUpperCase in this context

只有clojure function可以作为参数, 对于Java method需要用memfn封装一下
The problem is that toUpperCase( ) is a Java method, not a Clojure function.
This member-as-function idiom is a common one, so Clojure provides the “member function” memfn macro to wrap methods for you:

(map (memfn toUpperCase) ["a" "short" "message"])
("A" "SHORT" "MESSAGE")

As a preferred alternative to memfn, you can use an anonymous function to wrap a method call:

(map #(.toUpperCase %) ["a" "short" "message"])
("A" "SHORT" "MESSAGE")

instance?

Checking whether an object is an instance of a certain class. Clojure provides the instance? function for this purpose:

(instance? Integer 10)
true
(instance? Comparable 10)
true
(instance? String 10)
false

string format

Java provides a string format method. Because the message signature for Java’s format is slightly inconvenient to call in Clojure and because string formatting is so common, Clojure provides a wrapper: (format fmt-string & args)
You use format like this:

(format "%s ran %d miles today" "Stu" 8)
"Stu ran 8 miles today"

Optimizing for Performance

Using Primitives for Performance

In the preceding sections, function parameters carry no type information.
Clojure simply does the right thing. Depending on your perspective, this is either a strength or a weakness.
It’s a strength, because your code is clean and simple and can take advantage of duck typing.
But it’s also a weakness, because a reader of the code cannot be certain of data types and because doing the right thing carries some performance overhead.

Clojure的参数是没有type信息的, 当然比较简单, 清晰, 当然自然编译器需要做更多的事情, 所以如果对效率要求很高, 可以指定类型.

例子, 对于下面的例子, 求和

(defn sum-to [n]
  (loop [i 1 sum 0]
    (if (<= i n)
      (recur (inc i) (+ i sum))
      sum)))

对于没有指定参数类型版本的时间测试,

(dotimes [_ 5] (time (sum-to 10000))) ;dotimes will execute its body repeatedly, here 5 times
"Elapsed time: 0.778 msecs"
"Elapsed time: 0.559 msecs"
"Elapsed time: 0.633 msecs"
"Elapsed time: 0.548 msecs"
"Elapsed time: 0.647 msecs"

To speed things up, you can ask Clojure to treat n, i, and sum as ints:

(defn integer-sum-to [n]
  (let [n (int n)]
    (loop [i (int 1) sum (int 0)]
      (if (<= i n)
    (recur (inc i) (+ i sum))
    sum))))

可以看出, 确实指定类型后, 效率提高很多,

(dotimes [_ 5] (time (integer-sum-to 10000)))
"Elapsed time: 0.207 msecs"
"Elapsed time: 0.073 msecs"
"Elapsed time: 0.072 msecs"
"Elapsed time: 0.071 msecs"
"Elapsed time: 0.071 msecs"

Clojure’s convenient math operators (+, -, and so on) make sure their results do not overflow.
Maybe you can get an even faster function by using the unchecked version of +, unchecked-add:

对于操作不check是否overflow也可以提高效率, 不过这种优化不到万不得已最好别用, 因为难于维护, 一旦overflow导致数据出错, 难以排查.

(defn unchecked-sum-to [n]
  (let [n (int n)]
    (loop [i (int 1) sum (int 0)]
      (if (<= i n)
	(recur (inc i) (unchecked-add i sum))
	sum))))

Adding Type Hints

Clojure supports adding type hints to function parameters, let bindings, variable names, and expressions. These type hints serve three purposes:
• Optimizing critical performance paths
• Documenting the required type
• Enforcing the required type at runtime

(defn wants-a-string [#^String s] (println s))
#^String s 等于 #^{:tag String} s, 因为tag过于常用, 所以不写默认就是tag

Creating and Compiling Java Classes in Clojure

Often, Clojure's Java API will not be sufficient for integrating Java code with Clojure code.
Many Java libraries require you to implement a particular interface or extend a particular base class.
Fortunately, Clojure can create real Java classes, with methods that can be called like any other Java method, without requiring you to write any “wrapper” code in Java.

对于调用Java库, 有时候需要传入interface或class, 简单的例子是parse XML的sax库, 必须要传入实现了handles的类或接口, 所以需要在clojure里面直接生成java classes

先跳过, 后面再补充

To use a SAX parser, you need to implement a callback mechanism.
The easiest way is often to extend the DefaultHandler class. In Clojure, you can extend a class with the proxy function:

(proxy class-and-interfaces super-cons-args & fns)

(def print-element-handler
     (proxy [DefaultHandler] [] 
       (startElement          ;对于startElement的handle function  
	[uri local qname atts] 
	(println (format "Saw element: %s" qname)))))

 

Exception Handling

In Java code, exception handling crops up for three reasons:
• Wrapping checked exceptions (checked exceptions, http://blog.sina.com.cn/s/blog_492c74050100031s.html)

Checked Exceptions
Java’s checked exceptions must be explicitly caught or rethrown from every method where they can occur.
This seemed like a good idea at first: checked exceptions could use the type system to rigorously document error handling, with compiler enforcement. Most Java programmers now consider checked exceptions a failed experiment, because their costs in code bloat and maintainability outweigh their advantages. For more on the history of checked exceptions, see http://tinyurl.com/checked-exceptions-mistake


• Using a finally block to clean up nonmemory resources such as file and network handles
• Responding to the problem: ignoring the exception, retrying the  operation, converting the exception to a nonexceptional result, and so on

在java中为什么要做exception handling,

首先, 对于checked exceptions, 你必须handle, 要不catch, 要不rethrown
其次, 需要在finally block里面释放资源, 如文件和网络等资源
最后, 不想程序因为exception简单的停止, 需要做恢复和ignore操作. 比如爬网页, 一个网页发生异常, 需要retry几次, 或继续爬取


In Clojure, things are similar but simpler. The try and throw special forms give you all the capabilities of Java’s try, catch, finally, and throw.

But you should not have to use them very often, because of the following reasons:
• You do not have to deal with checked exceptions in Clojure. 貌似也只有Java有checked exceptions
• You can use macros such as with-open to encapsulate resource cleanup.

说白了, 前面两种在clojure里面都不需要或简单的处理, 只有第三种无法避免...

 

Keeping Exception Handling Simple

The absence of exception wrappers makes idiomatic Clojure code easier to read, write, and maintain than idiomatic Java. That said, nothing prevents you from explicitly catching, wrapping, and rethrowing exceptions in Clojure. It simply is not required. You should catch exceptions when you plan to respond to them in a meaningful way.

 

Cleaning Up Resources, 对于outside of garbage-collected memory
Garbage collection will clean up resources in memory. If you use resources that live outside of garbage-collected memory, such as file handles, you need to make sure that you clean them up, even in the event of an exception. In Java, this is normally handled in a finally block.
If the resource you need to free follows the convention of having a close method, you can use Clojure’s with-open macro:

(with-open [name init-form] & body)

Clojure使用with-open做了很好的封装, 你不用再自己去写finally block...

下面的例子, 在split里面open file, 使用with-open保证这个file一定会被close, 很简单.

(use '[clojure.contrib.duck-streams :only (spit)])
(spit "hello.out" "hello, world")

; from clojure-contrib
(defn spit [f content]
  (with-open [#^PrintWriter w (writer f)]
  (.print w content)))

当然你不满意默认的finally逻辑, 想自己实现也是可以的

If you need to do something other than close in a finally block, the Clojure try form looks like this:

(try expr* catch-clause* finally-clause?)
; catch-clause -> (catch classname name expr*)
; finally-clause -> (finally expr*)
 
(try
 (throw (Exception. "something failed"))
 (finally
  (println "we get to clean up")))

Responding to an Exception
The most interesting case is when an exception handler attempts to respond to the problem in a catch block.
As a simple example, consider writing a function to test whether a particular class is available at runtime:

例子, 测试类在当前环境中是否ready?

; not caller-friendly
(defn class-available? [class-name]
  (Class/forName class-name))

This approach is not very caller-friendly. The caller simply wants a yes/no answer but instead gets an exception:

(class-available? "borg.util.Assimilate")
java.lang.ClassNotFoundException: borg.util.Assimilate

问题就是不是很友好, 直接抛异常, 让人用的很别扭, 所以可以封装一下, 提供yes/no

; better-class-available
(defn class-available? [class-name]
  (try 
   (Class/forName class-name) true
   (catch ClassNotFoundException _ false))) 
原文地址:https://www.cnblogs.com/fxjwind/p/2886730.html