Convert List<T> to T... dynamically in a method call

Asked

Viewed 909 times

4

I’m in need of something that seems to be simple, but I’ve been doing some research and trying and I haven’t found anything that helps me.

Problem

I have a list of the kind List<T> I receive as a parameter of another method, and I have to make a call to a method that receives T... as a parameter.

Question

How would I dynamically convert a List<T> for T...?

Example

To illustrate, I’ll show you what I’m doing in my real situation:

private Object invokeMethodPrivateClassBase(Class<?> clazz,
        String nameMethod, List<Class<?>> paramsTypeClazz, Object... args) {
    try {
        // esse método 'getDeclaredMethod' recebe o primeiro parâmetro String e o segundo Object..., 
        // mas eu não posso receber 2 (dois) parametros '?...', pois só o ultimo pode ser desse tipo (por questões obvias).
        Method method = clazz.getDeclaredMethod(nameMethod, paramsTypeClazz);
        method.setAccessible(true);
        return method.invoke(AbsHorizontalListView.this, args);
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    }
    return null;
}
  • 4

    Converting to array does not work? Using the toArray?

  • @Wakim, yes, I had just seen this here, and see that T..., is nothing more than T[], then I’ll just take mine List<T> list and give a list.toArray() As you said, thanks. If you can/want to elaborate a good answer for future research, I’ll analyze it later. (There are times when simple things seem complicated, hehe).

2 answers

4


I have prepared a functional example of how the method with varied arguments should be called.

The main idea is you should pass an array of parameters to the invoke, only that the arguments of variable size must be another array that will be the last element of the main array.

Consider the comments in the code that explain step-by-step what it does:

class Exemplo {

    private static Object invokeMethodPrivateClassBase(
            Class<?> clazz,
            Object instance,
            String nameMethod, 
            List<Class<?>> paramsTypeClazz, 
            Object... args) {
        try {

            //localiza o método
            Method method = clazz.getDeclaredMethod(
                nameMethod, 
                paramsTypeClazz.toArray(new Class[paramsTypeClazz.size()]));

            //torna-o acessível mesmo sendo private
            method.setAccessible(true);

            //verifica se tem argumentos variados
            if (method.isVarArgs()) {

                //cria um array de parâmetros que deve ter a exata quantidade de parâmetros declarados,
                //isto é, sem considerar os argumentos variantes
                Object[] parameters = new Object[paramsTypeClazz.size()];

                //copia os parâmetros fixos para o novo array, desconsiderando apenas o último que é variante
                System.arraycopy(args, 0, parameters, 0, paramsTypeClazz.size() - 1);

                //calcula quantos argumentos variantes foram passados
                int varArgsSize = args.length - paramsTypeClazz.size() + 1;

                //cria um array com o tipo dos parâmetros variantes
                Object varArgsParameter = Array.newInstance(
                    paramsTypeClazz.get(paramsTypeClazz.size() - 1).getComponentType(), 
                    varArgsSize);

                //copia todos os demais argumentos para o array de argumentos variantes
                System.arraycopy(args, paramsTypeClazz.size() - 1, varArgsParameter, 0, varArgsSize);

                //coloca o array de argumentos variantes como último parâmetro para o método a ser chamado
                //isso porque o "..." equivale a um array
                parameters[paramsTypeClazz.size() - 1] = varArgsParameter;

                //chama o método
                return method.invoke(instance, parameters);
            } else {
                return method.invoke(instance, args);
            }
        } catch (Throwable e) {
            //apenas para teste
            System.out.println(e.getMessage());
        }
        return "Y";
    }

    /**
     * Método de exemplo a ser chamado via reflexão 
     */
    public String meuMetodo(String label, Integer... valores) {
        Integer soma = 0;
        for (Integer v : valores) soma += v;
        return label + ": " + soma;
    }

    public static void main (String[] args) throws java.lang.Exception {

        //modo normal
        System.out.println(new Exemplo().meuMetodo("S1", 3, 4, 1));

        //prepara lista de tipos dos argumentos
        List<Class<?>> paramsTypeClazz = new ArrayList<Class<?>>();
        paramsTypeClazz.add(String.class);
        paramsTypeClazz.add(Integer[].class);

        //modo reflexivo
        System.out.println(Exemplo.invokeMethodPrivateClassBase(
            Exemplo.class,
            new Exemplo(),
            "meuMetodo",
            paramsTypeClazz,
            "S1", 3, 4, 1));
    }
}

Demo no Ideone

  • 1

    It is even more than was expected for the question, as it did not even need to call a method by Reflection that contained varargs or "varied arguments". Great explanation and implementation, covering all scenarios. + 1 (A good response I expected in that other question, hehe)

  • @Fernando Kyllopardiun’s answer is basically what I would recommend. There, putting the class in the same package does not work because Java implements security restrictions that prevent the developer from creating classes that run in the same package of internal classes of it, preventing them from accessing attributes and methods that they should not. And the solution by Reflection it’s no secret, just use the setAccessible even.

3

This class method Class of the Java API receives a string and a Class<?>. The real problem then is to convert your List<Class<?>> paramsTypeClazz for varargs or an array. In this case try the following (untested):

Class<?>[] arrayDeClass = paramsTypeClass.toArray(new Class<?>[paramsTypeClass.size()]);
Method method = clazz.getDeclaredMethod(nameMethod, arrayDeClass);

Any doubt is just return. Hugs

  • Yes it worked properly, had not seen that varargs are the same thing as Array or T... = T[], until @Wakim quoted in the comments of the question. Thanks for the clarification. + 1

Browser other questions tagged

You are not signed in. Login or sign up in order to post.