使用Java反射,可以通过getConstructor(klass, args)
获得构造函数。
但是,当我们将构造函数签名中指定的类的派生类作为args
传递时,它将失败。如何克服这个问题?
例如,
HashSet.class.getConstructor(new Class[]{ HashSet.class });
失败。而
HashSet.class.getConstructor(new Class[]{ Collection.class });
成功。
我正在寻找一些可以在clojure
中轻松使用的东西。因此,我更希望有一些开箱即用的东西,而不必添加用户定义的函数。
你知道怎么解决这个问题吗?
HashSet
没有HashSet(HashSet)
构造函数,所以当你要求它时,你自然不会得到它。你必须通过与赋值兼容的类(至少循环通过supers,可能还有实现的接口和它们的supers)来找到一个。
这里有一个相当简单的方法。getConstructorForArgs
-方法遍历给定类中的所有构造函数,并检查构造函数的参数是否与给定的参数匹配(注意,给定的参数必须与构造函数中的顺序相同)。接口和子类的实现也可以工作,因为"兼容性"是通过调用构造函数参数的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
(Collection
-实现)为参数的构造函数,以及一个以Collection
为参数的构造器,则该方法在搜索接受HashSet
作为参数的构造函数时可以返回其中一个,这取决于在类中迭代时哪个先返回。如果它也需要适用于此类情况,您需要首先收集所有可能的与isAssignableFrom
匹配的候选者,然后对候选者进行更深入的分析,以选择最适合的候选者。
基于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
->Long
等映射添加到convm
并在下面的count-steps
中调整if
条件来完成。
这是代码:
(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)))))
不要混淆这里的多态行为。因为,在(new Class[]{Collection})中将Collection作为具体值而非参数类型传递。
我认为,您可以获得父类和所有实现接口的列表-->,这样您就可以首先检查Hashset的构造函数。如果什么都找不到,您可以递归地对所有父类和接口执行此操作,直到找到匹配的父类和界面为止。