【问题标题】:Why does class.getConstructor(parameters) not allow child objects as parameters?为什么 class.getConstructor(parameters) 不允许子对象作为参数?
【发布时间】:2021-09-26 10:45:54
【问题描述】:

假设我有一个父类和一个扩展我的父类的子类,并且我有以下代码。

public class SomeClass {
  private Parent myParent;

  public SomeClass(Parent myParent) {
    this.myParent = myParent;
  }
}

为什么允许这样做

Class<SomeClass> clazz = SomeClass.class;
SomeClass someClass = clazz.getConstructor(Parent.class).getInstance(Child);

这不是

Class<SomeClass> clazz = SomeClass.class;
SomeClass someClass = clazz.getConstructor(Child.class).getInstance(Child);

第二个抛出 NoSuchMethodeException。为什么在这种情况下没有动态绑定但是​​使用普通的构造函数动态绑定就可以了? 有没有办法解决这个问题?

编辑: 我正在尝试在运行时加载 jar 文件。此时我有我需要的类,加载了URLClassLoader 。接下来,我想创建一个已加载类的新实例。要创建实例,我调用urlClassLoader.loadClass(nameOfClass).getConstructor(parameterType).newInstance(initArguments); 在这种情况下,parameterType 将是 child.class

【问题讨论】:

  • "为什么在这种情况下没有动态绑定,但是使用普通的构造函数动态绑定就可以了?" - From Class::getConstructor's documentation: "返回一个Constructor 对象,它反映了此 Class 对象所表示的类的指定公共构造函数。parameterTypes 参数是一个 Class 对象数组,用于标识构造函数的形参类型,在声明的顺序...”
  • @Turing85 谢谢,但是为什么 java 不能确定孩子是否属于父类型?我应该如何解决这个问题?
  • @ufukguenes 谁说java不能?这不是这种确切方法的作用。这样做很可能是为了避免歧义,如果一个类扩展了另一个类并实现(至少一个)接口,就会发生歧义。
  • 如果您能告诉我们您想要做什么,我们或许可以为您提供更好的帮助。在使用 getConstructor(...) 方法时,没有特定的方法可以告诉 Java“检查是否有一个构造函数接受这种类型的超类型作为参数”。但是,Java 允许您在调用SomeClass 构造函数时传递Child 的实例,例如在表达式new SomeClass(childInstance) 中。

标签: java class constructor classloader


【解决方案1】:

下面的方法getConstructorAcceptingSupertype 找到一个接受给定参数类型或其超类型之一的构造函数。

import java.lang.reflect.Constructor;

public class ConstructorTester {
    
    public ConstructorTester(Object o) {
        System.out.println("Object constructor");
    }
    
    public ConstructorTester(CharSequence o) {
        System.out.println("CharSequence constructor");
    }
    
    public static void main(String[] args) throws Throwable {
        Object[] arg = {null};
        getConstructorAcceptingSupertype(ConstructorTester.class, Object.class).newInstance(arg); //Object constructor
        getConstructorAcceptingSupertype(ConstructorTester.class, CharSequence.class).newInstance(arg); //CharSeq constructor
        getConstructorAcceptingSupertype(ConstructorTester.class, Integer.class).newInstance(arg); //Object constructor
        getConstructorAcceptingSupertype(ConstructorTester.class, String.class).newInstance(arg); //CharSeq constructor
        getConstructorAcceptingSupertype(ConstructorTester.class, StringBuilder.class).newInstance(arg); //CharSeq constructor
    }
    
    /**
     * Returns a one-arg {@link Constructor} from {@code clazz} that accepts the given {@code parameterType}. This
     * method guarantees that there is no other one-arg constructor in {@code clazz} that accepts a superclass or
     * superinterface of the type that the returned constructor accepts. 
     */
    private static Constructor<?> getConstructorAcceptingSupertype(Class<?> clazz, Class<?> parameterType) {
        Constructor<?> correctConstructor = null;
        for(Constructor<?> constructor : clazz.getConstructors()) {
            if( constructor.getParameterCount() == 1 &&
                constructor.getParameters()[0].getType().isAssignableFrom(parameterType)) {
                if(correctConstructor == null) {
                    correctConstructor = constructor;
                }
                else { //see if this constructor is more specific than the current correctConstructor.
                    Class<?> currentType = correctConstructor.getParameters()[0].getType();
                    Class<?> newType = constructor.getParameters()[0].getType();
                    if(currentType.isAssignableFrom(newType))
                        correctConstructor = constructor;
                }
            }
        }
        if(correctConstructor == null)
            throw new IllegalArgumentException("No one-arg constructor exists that accepts the given parameter type");
        return correctConstructor;
    }
    
}

请注意,没有办法避免一个模棱两可的情况,比如C,扩展了另外两个接口AB,并且你的类有两个构造函数(SomeClass(A)SomeClass(B)) .在这种情况下,即使是像new SomeClass(instanceOfC) 这样的显式构造函数调用也会在编译时失败。在这种情况下,我上面的方法将返回任意构造函数(SomeClass(A)SomeClass(B)),但不保证是哪个。

【讨论】:

  • 这似乎是一个解决方案,只有当我的 parameterCount 始终等于 1 时才有效。但我当然可以有第二个循环来比较多个参数,对吧?
  • 我的解决方案假设您只有一个参数,是的。如果您需要找到接受多个参数的构造函数,则歧义甚至有更多的可能性。处理寻找最具体的方法/构造函数的The section in the JLS 非常复杂。如果您不想担心找到最特定的构造函数,而只想找到接受所有参数超类型的任何构造函数,那么这绝对是可行的.这是你需要的吗?
【解决方案2】:

问题是w.l.o.g. 无法解决,因为我们可以创建模棱两可的示例。考虑以下示例:

interface Foo {}

class Bar {
  public Bar() {}

  public Bar(Bar other) {}

  public Bar(Foo other) {}
}

class Baz extends Bar implements Foo {}

如果我们现在调用new Bar(new Baz()),程序将无法编译。

Ideone demo

因此,Class.getConstructor(Class&lt;?&gt;... parameterTypes) interprets the types as formal parameters, not actual parameters


我强烈建议不要实施在上述情况下为我们“选择”构造函数的解决方案。相反,我会选择:

  • 要么抛出一些Exception,表示无法确定单数构造函数,要么
  • 返回List&lt;Constructor&lt;?&gt;&gt;

对于第二种方法(返回List&lt;...&gt;),我们可以使用以下代码:

public static List<Constructor<?>> getConstructorsFromTypeFittingActualParameters(
    Class<?> type,
    Class<?>... actualParameters) {
  final Class<?>[] actualParametersNotNull = 
      Optional.ofNullable(actualParameters).orElseGet(() -> new Class<?>[0]);
  return Arrays.stream(type.getConstructors())
      .filter(constructor -> constructor.getParameterCount() == actualParametersNotNull.length)
      .filter(constructor -> formalParameterTypesAcceptActualParameterTypes(
          constructor.getParameterTypes(),
          actualParametersNotNull))
      .collect(Collectors.toList());
}

private static boolean formalParameterTypesAcceptActualParameterTypes(
    Class<?>[] formalParameterTypes,
    Class<?>[] actualParameterTypes) {
  Objects.requireNonNull(formalParameterTypes);
  Objects.requireNonNull(actualParameterTypes);
  if (formalParameterTypes.length != actualParameterTypes.length) {
    throw new IllegalArgumentException();
  }

  for (int index = 0; index < formalParameterTypes.length; ++index) {
    if (!formalParameterTypes[index].isAssignableFrom(actualParameterTypes[index])) {
      return false;
    }
  }
  return true;
}

给定上面的例子,这个实现将返回两个构造函数。

Ideone example

【讨论】:

    猜你喜欢
    • 2023-03-27
    • 2012-02-14
    • 1970-01-01
    • 1970-01-01
    • 2015-02-19
    • 2013-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多