2010-09-20 25 views
16

Trong Clojure, được đặt tên lớp là một chuỗi, tôi cần tạo một thể hiện mới của lớp. Nói cách khác, làm thế nào tôi sẽ thực hiện mới sơ thẩm-từ-class-name trongClojure: tạo thể hiện mới từ tên lớp String

(def my-class-name "org.myorg.pkg.Foo") 
; calls constructor of org.myorg.pkg.Foo with arguments 1, 2 and 3 
(new-instance-from-class-name my-class-name 1 2 3) 

Tôi đang tìm kiếm một giải pháp hơn nữa tao nhã hơn

  • gọi phương thức Java newInstance trên một constructor từ lớp
  • sử dụng eval, tải chuỗi, ...

Trong thực tế, tôi sẽ sử dụng nó trên các lớp học tạo ra bằng defrecord. Vì vậy, nếu có bất kỳ cú pháp đặc biệt cho kịch bản đó, tôi sẽ khá quan tâm.

Trả lời

23

Có hai cách hay để thực hiện việc này. Điều nào là tốt nhất phụ thuộc vào hoàn cảnh cụ thể.

Đầu tiên là phản ánh:

 
(clojure.lang.Reflector/invokeConstructor 
    (resolve (symbol "Integer")) 
    (to-array ["16"])) 

Đó là như gọi (new Integer "16") ... bao gồm bất kỳ đối số ctor khác mà bạn cần trong vector to-mảng. Điều này là dễ dàng, nhưng chậm hơn trong thời gian chạy hơn sử dụng new với các gợi ý loại đầy đủ.

Lựa chọn thứ hai là càng nhanh càng tốt, nhưng một chút phức tạp hơn, và sử dụng eval:

 
(defn make-factory [classname & types] 
    (let [args (map #(with-meta (symbol (str "x" %2)) {:tag %1}) types (range))] 
    (eval `(fn [[email protected]] (new ~(symbol classname) [email protected]))))) 

(def int-factory (make-factory "Integer" 'String)) 

(int-factory "42") 

Điểm mấu chốt là mã eval định nghĩa một chức năng mang tính chất như make-factory làm. Đây là chậm - chậm hơn ví dụ phản chiếu ở trên, do đó, chỉ thực hiện càng ít càng tốt, chẳng hạn như một lần cho mỗi lớp học. Nhưng bạn đã thực hiện rằng bạn có một hàm Clojure thông thường mà bạn có thể lưu trữ ở đâu đó, trong một var như int-factory trong ví dụ này hoặc trong bản đồ băm hoặc véc-tơ tùy thuộc vào cách bạn sẽ sử dụng nó. Bất kể, chức năng nhà máy này sẽ chạy ở tốc độ được biên dịch đầy đủ, có thể được HotSpot inline, vv và sẽ luôn chạy nhiều hơn nhanh hơn so với ví dụ phản chiếu.

Khi bạn giao dịch cụ thể với các lớp được tạo bởi deftype hoặc defrecord, bạn có thể bỏ qua danh sách loại vì các lớp đó luôn có chính xác hai ctors với các vị trí khác nhau. Điều này cho phép một cái gì đó như:

 
(defn record-factory [recordname] 
    (let [recordclass ^Class (resolve (symbol recordname)) 
     max-arg-count (apply max (map #(count (.getParameterTypes %)) 
             (.getConstructors recordclass))) 
     args (map #(symbol (str "x" %)) (range (- max-arg-count 2)))] 
    (eval `(fn [[email protected]] (new ~(symbol recordname) [email protected]))))) 


(defrecord ExampleRecord [a b c]) 

(def example-record-factory (record-factory "ExampleRecord")) 

(example-record-factory "F." "Scott" 'Fitzgerald) 
+0

Tuyệt vời! Lựa chọn thứ hai rõ ràng là một kỹ thuật rất chung chung. Tôi đã sử dụng nó theo một cách khác. – chris

4

Vì 'mới' là một dạng đặc biệt, tôi không chắc bạn có thể làm điều này mà không có macro hay không. Dưới đây là một cách để thực hiện việc sử dụng macro:

user=> (defmacro str-new [s & args] `(new ~(symbol s) [email protected])) 
#'user/str-new 
user=> (str-new "String" "LOL") 
"LOL" 

Kiểm tra nhận xét của Michal về giới hạn của macro này.

+2

Lưu ý rằng điều này sẽ chỉ làm việc nếu 's' nhận được vĩ mô là một (chữ) và không phải là một biểu thức tùy ý đánh giá một chuỗi. Trong trường hợp thứ hai, không có việc tránh 'eval' hoặc xây dựng cá thể phản chiếu. –

+0

Cảm ơn bạn đã chỉ ra điều đó. Không nghĩ đến chuyện đó. – Rayne

+0

Tôi sợ rằng s sẽ không phải là một chuỗi chữ. Tôi đã chỉnh sửa câu hỏi để phản ánh điều này. – chris

2

Trong Clojure 1.3, defrecord sẽ tự động defn một chức năng nhà máy sử dụng tên kỷ lục với "->" prepended. Tương tự, một biến thể có bản đồ sẽ là tên bản ghi được thêm vào với "map->".

user=> (defrecord MyRec [a b]) 
user.MyRec 
user=> (->MyRec 1 "one") 
#user.MyRec{:a 1, :b "one"} 
user=> (map->MyRec {:a 2}) 
#user.MyRec{:a 2, :b nil} 

Một vĩ mô như thế này nên làm việc để tạo ra một thể hiện từ tên chuỗi của các loại kỷ lục:

(defmacro newbie [recname & args] `(~(symbol (str "->" recname)) [email protected])) 
Các vấn đề liên quan