使用Java反射,可以通过getConstructor(klass, args)
获得构造函数。
但是,当我们以args
传递构造函数签名中指定的类的派生类时,它会失败。如何克服这个问题?
例如,
HashSet.class.getConstructor(new Class[]{ HashSet.class });
失败。而
HashSet.class.getConstructor(new Class[]{ Collection.class });
成功。
我正在寻找可以在clojure
中轻松使用的内容。因此,我更喜欢开箱即用而不必添加用户定义的功能。
任何想法,如何解决这个问题?
答案 0 :(得分:5)
HashSet
有没有HashSet(HashSet)
构造函数,所以当你提出它时,你自然不会得到它。你必须通过赋值兼容的类(至少循环遍历超级,可能是实现的接口和他们的超级)来找到一个。
答案 1 :(得分:5)
这是一种相当简单的方法。 getConstructorForArgs
-method遍历给定类中的所有构造函数,并检查构造函数的参数是否与给定的参数匹配(请注意,给定的参数必须与构造函数中的顺序相同)。接口和子类的实现也起作用,因为通过为构造函数参数调用isAssignableFrom
来检查“兼容性”(是给定的参数类型可赋予构造函数中的参数类型)。
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}) );
}
}
编辑:请注意,此解决方案并非适用于所有情况:如果有两个构造函数,其参数可从给定参数类型分配,则将选择第一个,甚至如果第二个更适合。例如,如果SomeClass
有一个构造函数,它将HashSet
(A Collection
- 实现)作为参数,而构造函数将Collection
作为参数,在搜索接受HashSet
作为参数的构造函数时,方法可以返回一个,具体取决于在迭代类时首先出现的那个。如果它也需要适用于此类情况,您需要首先收集与isAssignableFrom
匹配的所有候选人,然后对候选人进行更深入的分析,以选择最适合的人选。
答案 2 :(得分:4)
以esaj和T.J. Crowder的答案为基础:
以下命令返回给定类的一系列构造函数,这些构造函数是(1)可以使用指定的参数类型调用,并且(2)是最优的,因为它们的声明参数类型被继承阶梯上的最小步数删除来自指定的参数类型。 (因此,完全匹配将始终单独返回;如果有两个构造函数需要从某些指定的参数类型转换为其祖父类型,并且没有更接近的匹配,则它们都将被返回;如果没有匹配的构造函数根本不会返回nil
。)原始参数类型可以指定为符号或关键字(即'int
/ :int
)。最后,原始类型被认为等同于它们的盒装对应物。
示例:
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)>)
有人可能希望允许扩大数字转换次数;这可以做到,例如添加Integer
- &gt; Long
等映射到convm
并调整if
下面的count-steps
条件。
以下是代码:
(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)))))
答案 3 :(得分:0)
不要在此处混淆多态行为。因为,你将Collection作为具体值传递而不是param类型(new Class [] {Collection})。
答案 4 :(得分:0)
我认为,您可以获取父类和所有已实现接口的列表 - &gt;所以你可以先检查Hashset的构造函数。如果找不到任何内容,则可以递归执行所有父类和接口,直到找到匹配的类。