Используя Java Reflection, как получить конструктор класса, определяющий производный класс аргументов конструктора? - PullRequest
5 голосов
/ 09 февраля 2012

С помощью отражения Java можно получить конструктор через getConstructor(klass, args).

Однако, когда мы передаем в качестве args производный класс, указанный в сигнатуре конструктора, происходит сбой.Как решить эту проблему?

Например,

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

не удается.Пока

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

успешно.

Я ищу что-то, что можно было бы легко использовать в clojure.Поэтому я предпочел бы иметь что-то из коробки и не добавлять пользовательские функции.

Есть идеи, как решить эту проблему?

Ответы [ 5 ]

5 голосов
/ 12 февраля 2012

Вот довольно простой способ сделать это. 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 (A Collection -имплементация) в качестве параметра, и конструктор, принимающий Collection в качестве параметра, метод может вернуть любой из них при поиске конструктор, принимающий HashSet в качестве параметра, в зависимости от того, что было первым при переборе классов. Если это также необходимо для таких случаев, вам необходимо сначала собрать всех возможных кандидатов, которые соответствуют isAssignableFrom, а затем провести более глубокий анализ кандидатов, чтобы выбрать наиболее подходящего.

5 голосов
/ 09 февраля 2012

HashSet имеет конструктор нет HashSet(HashSet), поэтому, естественно, вы не получите его, когда запросите его.Чтобы найти один, вы должны пройти через классы, совместимые с назначением (по крайней мере, перебрать суперсайты и, возможно, реализованные интерфейсы и их суперпространства).

4 голосов
/ 13 февраля 2012

Опираясь на ответы 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 и изменив условие 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)))))
0 голосов
/ 12 февраля 2012

Я думаю, вы можете получить родительский класс и список всех реализованных Интерфейсов -> чтобы вы могли сначала проверить конструктор Hashset.Если ничего не найдено, вы можете сделать это рекурсивно для всех родительских классов и интерфейсов, пока не найдете подходящий.

0 голосов
/ 09 февраля 2012

Не путайте полиморфное поведение здесь. Потому что вы передаете коллекцию как конкретное значение, а не тип параметра в (новый класс [] {коллекция}).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...