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

时间:2015-02-14 22:43:20

标签: java reflection

我已经在Google和StackOverflow上搜索了但我还没有找到答案..

我尝试使用构造函数按名称实例化一个类,而不知道构造函数的参数类型。

  1. 我知道类的包和类名(它以字符串形式提供给方法)
  2. 我知道构造函数的参数(它们也被赋予方法,但作为对象数组)
  3. 它必须与此类似:

    // ...  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按名称和参数创建类的实例。

4 个答案:

答案 0 :(得分:1)

对于包含大量假设的简单情况:您循环遍历oa,在每个对象上调用getClass()并使用结果填充ca

如果您的例程必须更加健壮,则必须考虑更多情况:

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

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

答案 1 :(得分:0)

试试这个:

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;
}

我没有广泛测试过,但它似乎找到了原始类型的args的匹配,并选择了最具体的ctor来创建新对象。但是,它可能有错误(我发现它没有自动转换所选ctor的原始值,例如,如果选择ctor有short param而你在对象数组中传递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;
    }

}

答案 2 :(得分:0)

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

我使用this answer中的信息(关于构造函数中的原始类型的问题),@ Viktor Sorokin的answer和@ musiKk answer来提问我的问题。我只能通过知道类名/包路径和构造函数参数(作为对象数组)来创建类的实例,能够使用基元,对象,基元数组和对象数组。

代码很长,可以在:/

发布

答案 3 :(得分:-1)

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