Java 反射创建未知实例 class 和构造函数

Java reflection create instance of unknown class and constructor

我已经在 Google 和 Whosebug 上搜索过,但我还没有找到答案..

我尝试在不知道构造函数参数类型的情况下使用构造函数按名称实例化一个 class。

  1. 我知道 class 的包和 class 名称(它作为字符串提供给方法)
  2. 我知道构造函数的参数(它们也被提供给方法,但作为对象数组)

它必须是类似这样的东西:

// ...  Load class using ClassLoader if not in classpath ...

// These are given as arguments to the function:
String pncn = "Some.Package.UnknownClass"; // packagename classname
 Object[] oa =new Object[] { 
     "Test0", 
    new Random() 
};///object array

Class cl = Class.forName(pncn);

Class[] ca = /* ? */;
// (Here I need to get all constructor argument classes by the objects in 'oa'

// Get the constructor with the given arg types of 'ca'
 Constructor  co = cl.getConstructor(ca);

//And then instantiate the class
Object ro = co.newInstance(ca) ;

/*//return it:
return ro;*/;

所以基本上问题是如何将不同类型的对象数组(示例中的 'oa')转换为 Class 的数组?

我计划使用此方法通过 javascript.

按名称和参数创建 classes 的实例

试试这个:

private static Class<?>[] getClasses(Object[] oa) {
    if (oa == null) return new Class[0];
    Class<?>[] ret = new Class[oa.length];
    for (int i = 0; i < oa.length; i++) {
        ret[i] = oa[i].getClass();
    }
    return ret;
}

我没有广泛地测试以下内容,但它似乎找到了原始类型参数的匹配项,并选择了最具体的 ctor 来创建新对象。但是,它可能有错误(我发现的错误是它没有自动转换所选 ctor 的原始值,例如,如果所选 ctor 具有 short 参数并且您在对象数组中传递 int , 它会在 newInstance) 内失败。请随时对此进行改进:)​​

class B {
    @Override
    public String toString() {
        return "B{}";
    }
}
class D extends B {
    @Override
    public String toString() {
        return "D{}";
    }
}
class E extends D {
    @Override
    public String toString() {
        return "E{}";
    }
}

class Test {
    final short primitive;
    final B obj;

    Test() {
        System.out.println("()");
        primitive = 42;
        obj = new D();
    }

    Test(short primitive, B obj) {
        System.out.println("B()");
        this.primitive = primitive;
        this.obj = obj;
    }

    Test(short primitive, D obj) {
        System.out.println("D()");
        this.primitive = primitive;
        this.obj = obj;
    }

    @Override
    public String toString() {
        return "Test{" +
                "primitive=" + primitive +
                ", obj=" + obj +
                '}';
    }
}


class Junk {

    // sorts lists of param classes in order from the most to the least specific one
    private static final Comparator<? super Constructor<?>> CTOR_COMPARATOR = new Comparator<Constructor<?>>() {
        @Override
        public int compare(Constructor<?> ctorA, Constructor<?> ctorB) {
            Class<?>[] params1 = ctorA.getParameterTypes();
            Class<?>[] params2 = ctorB.getParameterTypes();

            if (params1.length != params2.length)
                throw new IllegalArgumentException(ctorA + " can't be compared to " + ctorB);

            for (int i = 0; i < params1.length; i++) {
                Class<?> aClass = params1[i];
                Class<?> bClass = params2[i];
                if (!aClass.equals(bClass)) {
                    if (aClass.isAssignableFrom(bClass)) return 1;
                    if (bClass.isAssignableFrom(aClass)) return -1;
                    throw new IllegalArgumentException(ctorA + " can't be compared to " + ctorB +
                            ": args at pos " + i + " aren't comparable: " + aClass + " vs " + bClass);
                }
            }

            return 0;
        }
    };

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        System.out.println(tryToCreateBestMatch(Test.class, new Object[]{(short)1, new B()}));
        System.out.println(tryToCreateBestMatch(Test.class, new Object[]{(short)1, new D()}));
        System.out.println(tryToCreateBestMatch(Test.class, new Object[]{null, new B()}));
        System.out.println(tryToCreateBestMatch(Test.class, new Object[]{(short)1, new E()}));
        System.out.println(tryToCreateBestMatch(Test.class, new Object[]{}));
        System.out.println(tryToCreateBestMatch(Test.class, new Object[]{"will fail"}));
    }

    private static <T> T tryToCreateBestMatch(Class<T> aClass, Object[] oa) throws InstantiationException, IllegalAccessException, InvocationTargetException {
        //noinspection unchecked
        Constructor<T>[] declaredConstructors = (Constructor<T>[]) aClass.getDeclaredConstructors();
        Class<?>[] argClasses = getClasses(oa);
        List<Constructor<T>> matchedCtors = new ArrayList<>();
        for (Constructor<T> ctr : declaredConstructors) {
            Class<?>[] parameterTypes = ctr.getParameterTypes();
            if (ctorMatches(parameterTypes, argClasses)) {
                matchedCtors.add(ctr);
            }
        }

        if (matchedCtors.isEmpty()) return null;

        Collections.sort(matchedCtors, CTOR_COMPARATOR);
        return matchedCtors.get(0).newInstance(oa);
    }

    private static boolean ctorMatches(Class<?>[] ctrParamTypes, Class<?>[] argClasses) {
        if (ctrParamTypes.length != argClasses.length) return false;
        for (int i = 0; i < ctrParamTypes.length; i++) {
            Class<?> ctrParamType = ctrParamTypes[i];
            Class<?> argClass = argClasses[i];

            if (!compatible(ctrParamType, argClass)) return false;
        }
        return true;
    }

    private static boolean compatible(Class<?> ctrParamType, Class<?> argClass) {
        if (ctrParamType.isAssignableFrom(argClass)) return true;
        if (ctrParamType.isPrimitive()) return compareAgainstPrimitive(ctrParamType.getName(), argClass);
        return false;
    }

    private static boolean compareAgainstPrimitive(String primitiveType, Class<?> argClass) {
        switch (primitiveType) {
            case "short":case "byte" :case "int":case "long":
                return INTEGER_WRAPPERS.contains(argClass.getName());
            case "float":case "dobule":
                return FP_WRAPPERS.contains(argClass.getName());
        }
        throw new IllegalArgumentException("Unexpected primitive type?!?!: " + primitiveType);
    }

    private static final HashSet<String> INTEGER_WRAPPERS = new HashSet<>(Arrays.asList(
            "java.lang.Integer", "java.lang.Short", "java.lang.Byte", "java.lang.Long"
            ));
    private static final HashSet<String> FP_WRAPPERS = new HashSet<>(Arrays.asList(
            "java.lang.Float", "java.lang.Double"
            ));

    private static Class<?>[] getClasses(Object[] oa) {
        if (oa == null) return new Class[0];
        Class<?>[] ret = new Class[oa.length];
        for (int i = 0; i < oa.length; i++) {
            ret[i] = oa[i] == null ? Object.class : oa[i].getClass();
        }
        return ret;
    }

}

对于有很多假设的简单情况:循环 oa,对每个对象调用 getClass() 并用结果填充 ca

如果你的例程必须更健壮一点,你必须考虑更多的情况:

  • 您必须检查 null。由于 null 对于每个引用类型都是可接受的,因此您可能还必须遍历所有可能的构造函数才能找到匹配项。不能保证是明确的匹配。
  • 然后还有原语。如果构造函数有一个原始参数,你也必须检查它,因为你的对象数组将只包含引用类型,因此 getClass() 将 return class 对象 java.lang.Integer 而不是 Integer.TYPE,基本类型 int 的 class 对象。

简而言之:您必须重新实现编译器已经为静态情况所做的工作。

最简单的方法是使用 java.beans.Expression. 它包含与编译器相同的所有类型逻辑。只需执行 "new MyClass" 形式的字符串表达式并提供对象数组作为参数。 Expression 的值是新对象。手动操作非常困难,因为您必须处理每个参数的原语和基数 类。

经过 3 小时的研究和尝试不同的解决方案,我终于让它工作了。

我使用了 this answer (to a question about primitive types in constructors), @Viktor Sorokin's and @musiKk's 中的信息来回答我的问题。我可以通过只知道 class name/package 路径和构造函数参数(作为对象数组)来创建 classes 的实例,能够使用基元、对象、基元数组和数组对象数。

代码太长 post 这里 :/