Class.getDeclaredConstructor 不检索兼容的参数超类型构造函数
Class.getDeclaredConstructor doesn't retrieve compatible argument supertype constructors
在我的程序中,我将 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.class
是 JTextField.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);
即使 C
是 B
的子类型,它也会抛出 java.lang.NoSuchMethodException
。这是完整的代码:
/**
* 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 调用它的方式调用构造函数。
这就是所有(或大多数)反射操作的工作方式。他们只期望和匹配声明的类型。在这种情况下,Class#getDeclareConstructor(Class...)
Returns a Constructor
object that reflects the specified public
constructor of the class represented by this Class object. The
parameterTypes
parameter is an array of Class
objects that identify
the constructor's formal parameter types, in declared order. If this
Class
object represents an inner class declared in a non-static
context, the formal parameter types include the explicit enclosing
instance as the first parameter.
您需要检查自己的参数类型是否是 declared/formal 类型的子类型。
你可以循环执行此操作
// 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)。
您还可以遍历构造函数并找到匹配的构造函数(这可能要快得多,因为您通常不会在 class 中声明那么多构造函数)
@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
}
你可以在一行中正确地做到这一点 java.beans.Expression:
C c;
A a = (A)new Expression(A.class, "new", new Object[]{c}).getValue();
这是 Sotirios 代码的高级版本。
我添加了一个 newInstance
方法,您可以在其中直接传递 class 和参数并获取返回的新实例。我添加了空检查和正确的错误处理 + 漂亮的错误消息。
并且我将 getDeclaredConstructors()
更改为 getConstructors()
所以我们只考虑 public 我们可以实际调用的构造函数。此更改取决于要求,在某些情况下可能必须恢复。
/**
* 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
*
* @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();
}
在我的程序中,我将 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.class
是 JTextField.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);
即使 C
是 B
的子类型,它也会抛出 java.lang.NoSuchMethodException
。这是完整的代码:
/**
* 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 调用它的方式调用构造函数。
这就是所有(或大多数)反射操作的工作方式。他们只期望和匹配声明的类型。在这种情况下,Class#getDeclareConstructor(Class...)
Returns a
Constructor
object that reflects the specified public constructor of the class represented by this Class object. TheparameterTypes
parameter is an array ofClass
objects that identify the constructor's formal parameter types, in declared order. If thisClass
object represents an inner class declared in a non-static context, the formal parameter types include the explicit enclosing instance as the first parameter.
您需要检查自己的参数类型是否是 declared/formal 类型的子类型。
你可以循环执行此操作
// 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)。
您还可以遍历构造函数并找到匹配的构造函数(这可能要快得多,因为您通常不会在 class 中声明那么多构造函数)
@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
}
你可以在一行中正确地做到这一点 java.beans.Expression:
C c;
A a = (A)new Expression(A.class, "new", new Object[]{c}).getValue();
这是 Sotirios 代码的高级版本。
我添加了一个 newInstance
方法,您可以在其中直接传递 class 和参数并获取返回的新实例。我添加了空检查和正确的错误处理 + 漂亮的错误消息。
并且我将 getDeclaredConstructors()
更改为 getConstructors()
所以我们只考虑 public 我们可以实际调用的构造函数。此更改取决于要求,在某些情况下可能必须恢复。
/**
* 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
*
* @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();
}