使用数组用varargs调用构造函数

时间:2014-12-09 16:02:31

标签: java reflection

假设您有一个字符串,其中包含您要实例化的类的名称,此外您还有一个包含逗号的字符串,用于分隔构造函数的参数。您可以假设构造函数仅将字符串作为String的参数或变量。您不知道在使用哪些类之前,您只能使用反射检查它们。

示例:

String clazzName = "com.example.Sample";
String parameters = "Apple,Cow,Red"

// can be used to instantiate this class

public class Sample {
    public Sample(String fruit, String animal, String color) { }
}

我使用以下逻辑编写了一个算法:

  1. 将字符串拆分为数组parameterArray(在示例中为parameterArray=["Apple", "Cow", "Red"]
  2. 构建类型为clazzArray的数组Class<?>[],其中包含String.class n次,其中ne是parameterArray的长度(在示例中:clazzArray=[String.class, String.class, String.class])< / LI>
  3. 使用反射搜索构造函数并调用它:Class.forName(clazzName).getConstructor(clazzArray).newInstance(parameterArray);
  4. 这很好用,现在假设你有一个类接受其中一个参数的varargs:

    String clazzName = "com.example.VASample";
    String parameters = "Party Host,Guest 1,Guest2,Guest3"
    
    // can be used to instantiate this class
    
    public class VASample {
        public VASample(String host, String... guests) { }
    }
    

    现在parameterArray是一个3元素数组,我(当然找不到构造函数)。我知道如果我在类上搜索[String, String[]]构造函数,我可以获得该构造函数。 但你怎么能知道呢?

    我正在搜索代码片段/算法/库,该代码片段/算法/库找到所提供的参数数组的构造函数,注意varargs。因此,为它提供[String, String, String]数组,它将匹配[String, String, String]构造函数,[String[]]构造函数(仅限varargs)或[String, String[]]构造函数(arg + varargs)。< / p>

    我知道java-compile(当然)在解析java源时会在内部处理它,但是如何使用反射模拟类似的东西?

2 个答案:

答案 0 :(得分:1)

您可以使用java.lang.reflect.Constructor#isVarArgs查看构造函数是否已使用varags定义。

您可以使用YourClass.class.getConstructors()遍历您的类的构造函数列表,然后检查哪个已使用varargs声明。

答案 1 :(得分:1)

在实施类似于Java的规则之前,您需要回忆一下规则:

  1. 如果存在完全匹配,请阅读不带varargs的匹配方法,首选该方法
  2. 如果没有完全匹配但是多个匹配的varargs方法,例如当您查找(String,String...)(String,String,String...)(String,String,String),查找失败并显示“模糊方法”错误
  3. 因此,您可以通过首先尝试直接查找来查找完全匹配来实现完整查找,并且只有在失败的情况下,迭代所有候选项,如果找到多个候选项,则会失败。

    以下代码执行此操作。与Java语言规则相比,它简化了,正如您在问题中所说的那样,只有String参数匹配,因此不会考虑扩大转换。

    static Object lookup(String clazzName, String parameters)
                                                   throws ReflectiveOperationException {
        String[] args=parameters.split(",");
        int nArg=args.length;
        Class<?>[] argType=new Class<?>[nArg];
        Arrays.fill(argType, String.class);
    
        Class<?> clazz=Class.forName(clazzName);
        Lookup l=MethodHandles.lookup();
        MethodHandle handle;
        try {
            // if there is an exact match (without varargs), Java prefers that
            handle=l.findConstructor(clazz, MethodType.methodType(void.class, argType));
        } catch(NoSuchMethodException ex) {
            // we have to search all candidates, like Java we reject ambiguous methods
            Constructor<?> found=null;
            find: for(Constructor<?> c: clazz.getConstructors()) {
                if(!c.isVarArgs()) continue;
                Class<?>[] param = c.getParameterTypes();
                int nConcrete = param.length-1;
                if(nConcrete>nArg || param[nConcrete]!=String[].class) continue find;
                for(int ix=0; ix<nConcrete; ix++)
                    if(param[ix]!=String.class) continue find;
                if(found!=null) throw new NoSuchMethodException(
                                                 "ambiguous: "+found+" and "+c+" match");
                found=c;
            }
            handle=l.unreflectConstructor(found);
        }
        try {
            return handle.invokeWithArguments((Object[])args);
        } catch(Throwable ex) {
            throw new InvocationTargetException(ex);
        }
    }
    

    它使用较新的调用API(MethodHandle s)和旧的Reflection的组合。优点是前者已经支持对调用端参数的varargs处理(将我们的扁平参数数组转换为具体参数和包含其余元素的数组的组合)。

    但它不支持迭代所有构造函数,因此对于该查找部分,我们必须回退到旧的Reflection API(Class.getConstructors())。