Class.getDeclaredConstructor不检索兼容的参数超类型构造函数

时间:2015-03-28 00:25:18

标签: java reflection

在我的程序中,我将JComponent类注册到我的类中,以便为我的目的处理它们(将它们的值转换为设置条目)。它看起来像这样:

 InputHandlers.register(InputJTextField.class,  javax.swing.JPasswordField.class);
 InputHandlers.register(InputJTextField.class,  JTextField.class);
 InputHandlers.register(InputJCheckBox.class,  JCheckBox.class);
 ...

我将这些注册值保存到Map并稍后检索。但是对于上面的示例,我遇到了一个问题:虽然javax.swing.JPasswordField.classJTextField.class的子类型,但Class.getDeclaredConstructor却看不到它。

我做了一个通用的例子,让这个问题更容易回答。考虑以下课程:

  class A {
    private final B b;
    public A(B b) {
      this.b = b;
    }
  }
  class B {}
  class C extends B {}

想象一下你想要这样做:

 A.class.getDeclaredConstructor(C.class);

即使java.lang.NoSuchMethodExceptionC的子类型,也会抛出B。这是完整的代码:

/**
 * Test how Class.getDeclaredConstructor seeks for constructors.
 * @author Jakub
 */
public class Constructors {
  public static class A {
    private final B b;
    public A(B b) {
      this.b = b;
    }
  }
  public static class B {}
  public static class C extends B {}
  public static void main(String[] args) //throws Exception
  {
    /** TRY USING REFLECTION **/
    //Make A from B
    tryAfromParam(new B());
    //Make A from C that is cast to B
    tryAfromParam((B)new C());
    //Make A from C without casting
    tryAfromParam(new C());
  }
  public static A tryAfromParam(Object param) {
    System.out.println("Try to make A from "+param.getClass()+" using A.class.getConstructor(...)");
    try {
      A a = AfromParam(param);
      System.out.println("    Sucess :)");
      return a;
    } catch (Exception ex) {
      System.out.println("    CONSTRUCTOR FAILED: "+ex);
    }
    return null;
  }
  public static A AfromParam(Object param) throws Exception {
    //Fetch the A's class instance
    Class cls = A.class;
    //Define constructor parameters
    Class[] arguments = new Class[] {
      param.getClass()
    };
    //Try to get the constructor
    Constructor<A> c = cls.getConstructor(arguments);
    //Try to instantiate A
    A a = c.newInstance(param);
    //Return result
    return a;
  }
}

问题是:如何找到与参数或任何超类型兼容的构造函数?请注意new A(new C())是有效的,因此反射应该以相同的方式工作 - 通常我想以Java调用它的方式调用构造函数。

3 个答案:

答案 0 :(得分:4)

您可以使用java.beans.Expression:

在一行中正确执行此操作
C c;
A a = (A)new Expression(A.class, "new", new Object[]{c}).getValue();

答案 1 :(得分:2)

这就是所有(或大多数)反射行动的工作方式。他们期望并仅匹配声明的类型。在这种情况下,Class#getDeclareConstructor(Class...)

  

返回反映指定public的Constructor对象   此Class对象表示的类的构造函数。的的   parameterTypes参数是一个标识Class个对象的数组   声明顺序的构造函数的形式参数类型。如果这   Class对象表示在非静态中声明的内部类   上下文,形式参数类型包括显式封闭   instance作为第一个参数。

如果您的参数类型是声明/正式类型的子类型,您需要检查自己。

您可以循环执行此操作

// Adjust for number of parameters
public static <T> Constructor<T> getConstructorDynamically(Class<T> clazz, Class<?> argumentType) {
    while (argumentType != null) {
        try {
            return clazz.getDeclaredConstructor(argumentType);
        } catch (NoSuchMethodException e) {
            argumentType = argumentType.getSuperclass();
        }
    }
    return null;
    // or throw
}

我确信有一些库可以做到,但我现在找不到它们(例如,看看Spring)。

你也可以遍历构造函数并找到匹配的构造函数(这可能要快得多,因为你通常不会在类中声明那么多的构造函数)

@SuppressWarnings("unchecked")
public static <T> Constructor<T> findConstructor(Class<T> clazz, Class<?>[] argumentTypes) {
    Constructor<T>[] constructors = (Constructor<T>[]) clazz.getDeclaredConstructors();
    for (Constructor<T> constructor : constructors) {
        // adapt for var args
        if (constructor.getParameterCount() != argumentTypes.length)
            continue;

        Class<?>[] formalTypes = constructor.getParameterTypes();
        for (int i = 0; i < formalTypes.length; i++) {
            if (!formalTypes[i].isAssignableFrom(argumentTypes[i]))
                continue;
        }
        return constructor;
    }
    return null; // or throw
}

答案 2 :(得分:0)

这是Sotirios的高级版本&#39;代码。

我添加了一个getDeclaredConstructors()方法,您可以直接传递类和args并返回新实例。我添加了空检查和正确的错误处理+很好的错误消息。

我将getConstructors()更改为/** * Creates a new instance for clazz using it's constructor matching the given args. * As opposed to the <code>clazz.getConstructor(...).newInstance(args)</code> method * this method considers also constructors with matching super-type parameters * (as we know it from normal method or constructor invocations). * * @param clazz * @param args * @return * @throws InstantiationException * @throws IllegalAccessException * @throws IllegalArgumentException * @throws InvocationTargetException * @throws NoSuchMethodException */ public static <T> T newInstance(Class<? extends T> clazz, Object... args) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { return findConstructor(clazz, args).newInstance(args); } /** * Adopted from http://stackoverflow.com/a/29312271/1520422 * * @param clazz * @param args * @return * @throws NoSuchMethodException */ @SuppressWarnings("unchecked") public static <T> Constructor<T> findConstructor(Class<T> clazz, Object... args) throws NoSuchMethodException { Constructor<T> matchingConstructor = null; Constructor<T>[] constructors = (Constructor<T>[]) clazz.getConstructors(); for (Constructor<T> constructor : constructors) { if (constructor.getParameterCount() != args.length) { continue; } Class<?>[] formalTypes = constructor.getParameterTypes(); for (int i = 0; i < formalTypes.length; i++) { if (!formalTypes[i].isInstance(args[i])) { continue; } } if (matchingConstructor != null) // already found one ... so there is more than one ... { throw new NoSuchMethodException("Multiple constructors found for: " + printArgs(clazz, args) + " --> " + matchingConstructor + " --> " + constructor); } matchingConstructor = constructor; } if (matchingConstructor == null) { throw new NoSuchMethodException("No constructor found for: " + printArgs(clazz, args)); } return matchingConstructor; } private static String printArgs(Class<?> clazz, Object... args) { StringBuilder msg = new StringBuilder(); msg.append("new "); msg.append(clazz.getName()); msg.append("("); for (int i = 0; i < args.length; i++) { if (i > 0) { msg.append(", "); } msg.append(args[i] == null ? "null" : args[i].getClass().getName()); } msg.append(")"); return msg.toString(); } ,因此我们只考虑实际可以调用的公共构造函数。此更改取决于要求,并且可能必须在某些情况下还原。

def __str__(self):
    space_sym = "&nbsp"*10 # hardcode magic-number of indent spaces
    return 'more spaces {}{}{}!'.format(self.foo, space_sym, self.bar)