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))) 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM