2012-02-09 35 views
5

Với sự phản chiếu Java, người ta có thể nhận được một hàm tạo thông qua getConstructor(klass, args).Sử dụng Java Reflection, làm thế nào để có được hàm tạo của một lớp xác định một lớp dẫn xuất của hàm tạo là args?

Tuy nhiên, khi chúng tôi chuyển thành args một lớp dẫn xuất của lớp được chỉ định trong chữ ký hàm tạo, nó không thành công. Làm cách nào để khắc phục vấn đề này?

Ví dụ,

HashSet.class.getConstructor(new Class[]{ HashSet.class }); 

thất bại. Trong khi

HashSet.class.getConstructor(new Class[]{ Collection.class }); 

thành công.

Tôi đang tìm kiếm thứ gì đó có thể dễ dàng được sử dụng trong clojure. Vì vậy, tôi muốn có một cái gì đó ra khỏi hộp và không phải thêm các hàm do người dùng định nghĩa.

Bất kỳ ý tưởng nào, cách giải quyết vấn đề này?

Trả lời

4

xây dựng trên câu trả lời của esajT.J. Crowder:

Lợi nhuận sau một seq nhà xây dựng cho lớp cho được (1) có thể được gọi với các loại đối số quy định và (2) tối ưu theo nghĩa là tuyên bố của họ các kiểu tham số được loại bỏ bởi một số lượng tối thiểu các bước lên bậc kế thừa từ các kiểu đối số đã chỉ định. (Vì vậy, một kết hợp chính xác sẽ luôn được trả về một mình, nếu có hai hàm tạo yêu cầu truyền từ một số kiểu đối số được chỉ định cho các kiểu ông bà của chúng, và không có kết hợp gần hơn, cả hai sẽ được trả về; ở tất cả, nil sẽ được trả lại.) Các loại đối số nguyên thủy có thể được chỉ định làm biểu tượng hoặc từ khóa (ví dụ: 'int/:int). Cuối cùng, các kiểu nguyên thủy được coi là tương đương với các đối tác đóng hộp của chúng.

Ví dụ:

user> (find-best-constructors java.util.HashSet [:int :float]) 
(#<Constructor public java.util.HashSet(int,float)>) 
user> (find-best-constructors java.util.HashSet [java.util.HashSet]) 
(#<Constructor public java.util.HashSet(java.util.Collection)>) 
user> (find-best-constructors java.util.HashSet [Integer]) 
(#<Constructor public java.util.HashSet(int)>) 

Người ta có thể muốn cho phép mở rộng chuyển đổi số; có thể được thực hiện ví dụ: bằng cách thêm Integer ->Long v.v. ánh xạ tới convm và tinh chỉnh điều kiện ifcount-steps bên dưới.

Dưới đây là các mã:

(defn find-best-constructors [klass args] 
     (let [keym {:boolean Boolean/TYPE 
        :byte Byte/TYPE 
        :double Double/TYPE 
        :float Float/TYPE 
        :int  Integer/TYPE 
        :long Long/TYPE 
        :short Short/TYPE} 
       args (->> args 
         (map #(if (class? %) % (keyword %))) 
         (map #(keym % %))) 
       prims (map keym [:boolean :byte :double :float :int :long :short]) 
       boxed [Boolean Byte Double Float Integer Long Short] 
       convm (zipmap (concat prims boxed) (concat boxed prims)) 
       ctors (->> (.getConstructors klass) 
         (filter #(== (count args) (count (.getParameterTypes %)))) 
         (filter #(every? (fn [[pt a]] 
              (or (.isAssignableFrom pt a) 
               (if-let [pt* (convm pt)] 
                (.isAssignableFrom pt* a)))) 
              (zipmap (.getParameterTypes %) args))))] 
      (when (seq ctors) 
      (let [count-steps (fn count-steps [pt a] 
           (loop [ks #{a} cnt 0] 
            (if (or (ks pt) (ks (convm pt))) 
            cnt 
            (recur (set (mapcat parents ks)) (inc cnt))))) 
        steps (map (fn [ctor] 
           (map count-steps (.getParameterTypes ctor) args)) 
          ctors) 
        m (zipmap steps ctors) 
        min-steps (->> steps 
           (apply min-key (partial apply max)) 
           (apply max))] 
       (->> m 
        (filter (comp #{min-steps} (partial apply max) key)) 
        vals))))) 
+0

Wow !! Khá một chút mã.Bạn có thể vui lòng cung cấp một tệp clj có thể chạy được thể hiện cách gọi hàm 'find-best-constructors' với' HashSet' chẳng hạn. – viebel

+0

Vâng, có ba ví dụ trong câu trả lời. Bạn có thể sao chép các biểu thức ở bên phải của 'user>' vào tệp REPL/của riêng bạn và xác minh rằng các giá trị được trả lại là như được quảng cáo. –

+0

Bằng cách nào đó, tôi gặp lỗi khi nhập HashSet. Bạn có thể truy cập tập tin của tôi tại: [find-constructors.clj] (https://github.com/viebel/Learning/blob/master/clojure/find-constructors.clj). – viebel

5

HashSet no HashSet(HashSet) hàm tạo, vì vậy tự nhiên bạn không nhận được khi bạn yêu cầu. Bạn phải làm việc theo cách của bạn thông qua các lớp tương thích gán (ít nhất là vòng lặp qua supers, và có lẽ các giao diện thực hiện và supers của họ) để tìm một.

+0

Tại sao không thể này được thực hiện bởi cơ sở hạ tầng mẫn? Tôi thực sự cần chức năng này cho một vấn đề khó khăn trong clojure. – viebel

+0

Nó không có sẵn bởi vì nó khó. Bạn sẽ phải sao chép tất cả các quy tắc Java Language Specification có liên quan được triển khai vào javac. Mã phản chiếu của Clojure bỏ qua một tập con lớn các cuộc gọi có thể cho phép (ví dụ: đánh đấm/bỏ hộp đối số), do đó đôi khi cần phải cung cấp loại gợi ý/truyền khi không cần thiết. –

+0

Khó khăn của nhiệm vụ này nên được thực hiện rõ ràng bởi thực tế là Sun thậm chí không bao gồm hỗ trợ cho nó trong API phản chiếu của riêng họ (do đó hỗ trợ một phần của Clojure thông qua Reflector). –

0

Đừng nhầm lẫn hành vi đa hình ở đây. Bởi vì, bạn đang chuyển Bộ sưu tập dưới dạng giá trị cụ thể không phải là loại tham số trong (Lớp mới [] {Bộ sưu tập}).

5

Đây là một cách khá đơn giản để thực hiện việc này. getConstructorForArgs -method đi qua tất cả các hàm tạo trong lớp đã cho và kiểm tra xem các tham số của hàm tạo có khớp với các tham số đã cho hay không (lưu ý rằng các tham số đã cho phải theo cùng thứ tự như trong hàm tạo). Việc triển khai các giao diện và các lớp con cũng hoạt động, vì "khả năng tương thích" được kiểm tra bằng cách gọi isAssignableFrom cho đối số hàm tạo (là kiểu tham số đã gán có thể gán cho kiểu tham số trong hàm tạo).

public class ReflectionTest 
{ 
    public Constructor<?> getConstructorForArgs(Class<?> klass, Class[] args) 
    { 
     //Get all the constructors from given class 
     Constructor<?>[] constructors = klass.getConstructors(); 

     for(Constructor<?> constructor : constructors) 
     { 
      //Walk through all the constructors, matching parameter amount and parameter types with given types (args) 
      Class<?>[] types = constructor.getParameterTypes(); 
      if(types.length == args.length) 
      {    
       boolean argumentsMatch = true; 
       for(int i = 0; i < args.length; i++) 
       { 
        //Note that the types in args must be in same order as in the constructor if the checking is done this way 
        if(!types[i].isAssignableFrom(args[i])) 
        { 
         argumentsMatch = false; 
         break; 
        } 
       } 

       if(argumentsMatch) 
       { 
        //We found a matching constructor, return it 
        return constructor; 
       } 
      } 
     } 

     //No matching constructor 
     return null; 
    } 

    @Test 
    public void testGetConstructorForArgs() 
    { 
     //There's no constructor in HashSet that takes a String as a parameter 
     Assert.assertNull(getConstructorForArgs(HashSet.class, new Class[]{String.class})); 

     //There is a parameterless constructor in HashSet 
     Assert.assertNotNull(getConstructorForArgs(HashSet.class, new Class[]{})); 

     //There is a constructor in HashSet that takes int as parameter 
     Assert.assertNotNull(getConstructorForArgs(HashSet.class, new Class[]{int.class})); 

     //There is a constructor in HashSet that takes a Collection as it's parameter, test with Collection-interface 
     Assert.assertNotNull(getConstructorForArgs(HashSet.class, new Class[]{Collection.class})); 

     //There is a constructor in HashSet that takes a Collection as it's parameter, and HashSet itself is a Collection-implementation 
     Assert.assertNotNull(getConstructorForArgs(HashSet.class, new Class[]{HashSet.class})); 

     //There's no constructor in HashSet that takes an Object as a parameter 
     Assert.assertNull(getConstructorForArgs(HashSet.class, new Class[]{Object.class})); 

     //There is a constructor in HashSet that takes an int as first parameter and float as second 
     Assert.assertNotNull(getConstructorForArgs(HashSet.class, new Class[]{int.class, float.class})); 

     //There's no constructor in HashSet that takes an float as first parameter and int as second 
     Assert.assertNull(getConstructorForArgs(HashSet.class, new Class[]{float.class, int.class})); 
    } 
} 

Sửa: Lưu ý rằng giải pháp này là không hoàn hảo cho tất cả các trường hợp: nếu có hai nhà thầu, có một tham số đó là chuyển nhượng từ một loại tham số nhất định, người đầu tiên sẽ được chọn, ngay cả khi thứ hai là phù hợp hơn. Ví dụ: nếu SomeClass sẽ có một hàm tạo có một số HashSet (A Collection -implementation) làm tham số và một hàm tạo tham số Collection làm tham số, phương pháp có thể trả về một trong hai khi tìm kiếm một hàm tạo chấp nhận tham số HashSet tùy thuộc vào điều gì đến trước khi lặp qua các lớp. Nếu cần phải làm việc cho những trường hợp như vậy, trước tiên bạn cần thu thập tất cả các ứng cử viên có thể, phù hợp với isAssignableFrom và sau đó thực hiện phân tích sâu hơn cho các ứng cử viên để chọn ứng cử viên phù hợp nhất.

+0

Rất đẹp. Nhưng tôi đang tìm cái gì đó có thể dễ dàng được sử dụng trong 'clojure'. Vì vậy, tôi muốn có một cái gì đó ra khỏi hộp và không phải thêm các hàm do người dùng định nghĩa. Đã cập nhật câu hỏi. – viebel

+1

@YehonathanSharvit Tôi không quen với clojure, nhưng bản thân phương thức có thể được chuyển đến lớp bạn chọn và khai báo tĩnh, vì vậy nếu bạn ít nhất có thể gọi phương thức tĩnh từ clojure, điều này sẽ hoạt động (xem các chỉnh sửa về các cạm bẫy có thể có) ở phần cuối của câu trả lời của tôi, mặc dù). – esaj

+0

Bạn có hiểu tại sao tính năng này không được cơ sở hạ tầng phản ánh hỗ trợ không? – viebel

0

Tôi nghĩ rằng, bạn có thể có được lớp cha và danh sách tất cả các giao diện được triển khai -> để bạn có thể kiểm tra hàm khởi tạo của Hashset trước. Nếu không có gì được tìm thấy, bạn có thể làm điều đó đệ quy cho tất cả các lớp cha và giao diện cho đến khi bạn tìm thấy một số phù hợp.

Các vấn đề liên quan