使用Java Reflection,如何获取指定构造函数args的派生类的类的构造函数?

vie*_*bel 5 java reflection clojure

使用Java反射,可以获得构造函数getConstructor(klass, args).

但是,当我们作为args构造函数签名中指定的类的派生类传递时,它将失败.如何克服这个问题?

例如,

HashSet.class.getConstructor(new Class[]{ HashSet.class });
Run Code Online (Sandbox Code Playgroud)

失败.而

HashSet.class.getConstructor(new Class[]{ Collection.class });
Run Code Online (Sandbox Code Playgroud)

成功.

我正在寻找可以轻松使用的东西clojure.因此,我更喜欢开箱即用,而不必添加用户定义的功能.

任何想法,如何解决这个问题?

T.J*_*der 5

HashSet 没有HashSet(HashSet)构造,所以自然你没有得到一个,当你问吧.你必须通过赋值兼容的类(至少循环遍历超级,可能是已实现的接口及其超级)来找到一个.


esa*_*saj 5

这是一个相当简单的方法.本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}) );
    }   
}
Run Code Online (Sandbox Code Playgroud)

编辑:请注意,此解决方案并非适用于所有情况:如果有两个构造函数,它们具有可从给定参数类型分配的参数,则将选择第一个,即使第二个更适合.例如,如果SomeClass有一个构造函数将HashSet(A Collection-implementation)作为参数,并且构造函数将a Collection作为参数,则该方法可以在搜索接受HashSetas参数的构造函数时返回一个,具体取决于哪个是第一个在迭代类时.如果它也需要为这种情况工作,你需要首先收集所有可能的候选人,isAssignableFrom然后对候选人进行更深入的分析,以选择最适合的人选.


Mic*_*zyk 4

基于esajTJ 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)>)
Run Code Online (Sandbox Code Playgroud)

人们可能希望允许扩大数字转换;例如,可以通过添加Integer->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)))))
Run Code Online (Sandbox Code Playgroud)