En Java 5 se introdujeron los parámetros de longitud variable, que permiten pasar un número indeterminado de argumentos al invocar un método. Los parámetros de longitud variable son azúcar sintáctico de Java; en esencia siguen basándose en la implementación de arrays:

void foo(String... args);
void foo(String[] args);
//方法签名 
([Ljava/lang/String;)V // public void foo(String[] args)

Definición de métodos

Al definir un método, se agregan tres puntos después del último parámetro formal…, lo que indica que ese parámetro formal puede aceptar múltiples valores de parámetros, y estos se pasan como un array. En la definición anterior hay varios puntos clave a tener en cuenta:

  • Los parámetros variables solo pueden ser el último parámetro de la función, pero delante de ellos puede haber o no otros parámetros.

  • Dado que el parámetro variable debe ser el último parámetro, una función solo puede tener como máximo un parámetro variable.

  • Los parámetros variables de Java son convertidos por el compilador en un array.

  • Después de compilarse a bytecode, el parámetro de longitud variable aparece en la firma del método en forma de array. Las firmas de estos dos métodos son idénticas, por lo que no pueden usarse como sobrecarga de métodos. Si aparecen a la vez, el código no compilará. Los parámetros variables son compatibles con los arrays, pero no a la inversa.

    public void foo(String...varargs){}
    
    foo("arg1", "arg2", "arg3");
    
    //上述过程和下面的调用是等价的
    foo(new String[]{"arg1", "arg2", "arg3"});
  • En J2SE 1.5 se añadió"genéricos"el mecanismo que permite parametrizar un tipo bajo ciertas condiciones. Por ejemplo, al escribir una clase, el tipo de un parámetro formal de un método puede representarse con un identificador (como T), y el tipo real que representa ese identificador se especifica al crear instancias de esta clase. Este mecanismo puede proporcionar una mayor reutilización de código y una verificación de tipos más estricta en tiempo de compilación. Sin embargo, el mecanismo de genéricos no puede combinarse con parámetros formales de número variable. Si se usa un identificador para representar el tipo de un parámetro formal que puede coincidir con un número indeterminado de argumentos reales, el compilador dará un"generic array creation"error.

    public class Varargs {
    
        public static void test(String... args) {
            for(String arg : args) {//当作数组用foreach遍历
                System.out.println(arg);
            }
        }
        //Compile error
        //The variable argument type Object of the method must be the last parameter
        //public void error1(String... args, Object o) {}
        //public void error2(String... args, Integer... i) {}
    
        //Compile error
        //Duplicate method test(String...) in type Varargs
        //public void test(String[] args){}
    }

Llamada a métodos con parámetros variables

Al invocar un método de parámetros variables, se pueden pasar de cero a cualquier número de argumentos; el compilador convierte los parámetros variables en un array. También se puede pasar directamente un array, como en el siguiente ejemplo:

public class Varargs {

    public static void test(String... args) {
        for(String arg : args) {
            System.out.println(arg);
        }
    }

    public static void main(String[] args) {
        test();//0个参数
        test("a");//1个参数
        test("a","b");//多个参数
        test(new String[] {"a", "b", "c"});//直接传递数组
    }
}

Sobrecarga de métodos

Priorizar la coincidencia de parámetros fijos

Al invocar un método sobrecargado, si la llamada puede coincidir tanto con el método sobrecargado de parámetros fijos como con el método sobrecargado de parámetros de longitud variable, se elige el método de parámetros fijos:

public class Varargs {

    public static void test(String... args) {
        System.out.println("version 1");
    }

    public static void test(String arg1, String arg2) {
        System.out.println("version 2");
    }
    public static void main(String[] args) {
        test("a","b");//version 2 优先匹配固定参数的重载方法
                test();//version 1
    }
}

> Coincidir con múltiples parámetros variables

Al invocar un método sobrecargado, si la llamada puede coincidir con dos métodos sobrecargados de parámetros de longitud variable, se produce un error de compilación:

public class Varargs {

    public static void test(String... args) {
        System.out.println("version 1");
    }

    public static void test(String arg1, String... arg2) {
        System.out.println("version 2");
    }
    public static void main(String[] args) {
        test("a","b");//Compile error
    }
}

Sobrescritura de métodos

Evitar la sobrecarga de métodos con parámetros de longitud variable

Aunque el compilador pueda determinar el método concreto a invocar priorizando la coincidencia con parámetros fijos, al leer el código es fácil caer en trampas. Hay que considerar cuidadosamente la sobrecarga de métodos con parámetros de longitud variable.

No dejes que los valores null y vacíos amenacen a los métodos de longitud variable

public class Client {  
     public void methodA(String str,Integer... is){       
     }  

     public void methodA(String str,String... strs){          
     }  

     public static void main(String[] args) {  
           Client client = new Client();  
           client.methodA("China", 0);  
           client.methodA("China", "People");  
           client.methodA("China");  //compile error
           client.methodA("China",null);  //compile error
     }  
}

Se modifica de la siguiente manera:

public static void main(String[] args) {  
     Client client = new Client();  
     String[] strs = null;  
     client.methodA("China",strs);  
}

Al hacer que el compilador sepa que este valor null es de tipo String, la compilación puede pasar sin problemas, reduciendo así la ocurrencia de errores.

Al sobrescribir métodos de longitud variable también hay que seguir las reglas

package com;
public class VarArgsTest2 {
    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        // 向上转型
        Base base = new Sub();
        base.print("hello");
        // 不转型
        Sub sub = new Sub();
        sub.print("hello");//compile error
    }
}
// 基类
class Base {
    void print(String... args) {
        System.out.println("Base......test");
    }
}
// 子类,覆写父类方法
class Sub extends Base {
    @Override
    void print(String[] args) {
        System.out.println("Sub......test");
    }
}

¿Por qué el primero compila? De hecho, el objeto base realiza una conversión ascendente (upcasting) del objeto sub de la subclase, y la lista de parámetros formales está determinada por la clase padre, por lo que, naturalmente, puede compilar. Veamos el caso de la llamada directa de la subclase: el compilador ve que la subclase ha sobrescrito el método print de la clase padre, por lo que sin duda usa el método print redefinido en la subclase. Aunque la lista de parámetros no coincida, no irá a la clase padre a buscar una coincidencia, porque si encuentra una, no sigue buscando. Por lo tanto, se produce el error de tipo no coincidente.

Este es un caso especial: la lista de parámetros del método sobrescrito puede incluso diferir de la de la clase padre, lo que viola la definición de sobrescritura y puede provocar errores inexplicables.

Aquí se resumen las condiciones que debe cumplir la sobrescritura:

  • El método sobrescrito no puede reducir el permiso de acceso.

  • La lista de parámetros debe ser la misma que la del método sobrescrito (incluida la forma explícita).

  • El tipo de retorno debe ser el mismo que el del método sobrescrito o un subtipo del mismo.

  • El método sobrescrito no puede lanzar nuevas excepciones comprobadas, ni excepciones fuera del ámbito de la clase padre; pero puede lanzar menos excepciones, más limitadas, o no lanzar ninguna.


Posibles problemas

UsarObject…como parámetro de longitud variable:

public void foo(Object... args) {
    System.out.println(args.length);
}

foo(new String[]{"arg1", "arg2", "arg3"}); //3
foo(100, new String[]{"arg1", "arg1"}); //2

foo(new Integer[]{1, 2, 3}); //3
foo(100, new Integer[]{1, 2, 3}); //2
foo(1, 2, 3); //3
foo(new int[]{1, 2, 3}); //1

int[]No se puede convertir aObject[], por lo tanto se trata como un simple objeto de array;Integer[]Se puede convertir aObject[], se puede usar como un array de objetos.


Precauciones al invocar métodos mediante reflexión

public class Test {
    public static void foo(String... varargs){
        System.out.println(args.length);
    }

    public static void main(String[] args){
        String[] varArgs = new String[]{"arg1", "arg2"};
        try{
            Method method = Test.class.getMethod("foo", String[].class);
            method.invoke(null, varArgs);
            method.invoke(null, (Object[])varArgs);
            method.invoke(null, (Object)varArgs);
            method.invoke(null, new Object[]{varArgs});
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

De las cuatro invocaciones anteriores, las dos primeras lanzarán en tiempo de ejecuciónjava.lang.IllegalArgumentException: wrong number of argumentsuna excepción, mientras que las dos últimas se invocan normalmente.

La reflexión se obtiene en tiempo de ejecución. Desde la perspectiva del tiempo de ejecución, los parámetros de longitud variable y los arrays son equivalentes, por lo que la firma del método es:

//方法签名
([Ljava/lang/String;)V // public void foo(String[] varargs)

Veamos de nuevo la declaración del método del objeto Method:

Object invoke(Object obj, Object... args)

Aunque args es un parámetro de longitud variable, su longitud está limitada por la longitud de la lista de parámetros del método real que representa el objeto Method. Y desde la firma en tiempo de ejecución, ([Ljava/lang/String;)V en realidad tiene solo un parámetro formal, es decir, String[] varargs. Por lo tanto, en invoke(Object obj, Object… args), la longitud del argumento real para el parámetro variable args solo puede ser 1.

//Object invoke(Object obj, Object... args)
//String[] varArgs = new String[]{"arg1", "arg2"};
method.invoke(null, varArgs); //varArgs长度为2,错误
method.invoke(null, (Object[])varArgs); //将String[]转换为Object[],长度为2的,错误
method.invoke(null, (Object)varArgs);//将整个String[] 转为Object,长度为1,符合
method.invoke(null, new Object[]{varArgs});//Object[]长度为1,正确。上一个和这个是等价的

¿Cuándo usar parámetros de longitud variable?

Stack OverflowHay una pregunta sobre el uso de parámetros de longitud variable. En pocas palabras,
Cuando no se está seguro de la cantidad de objetos que el método necesita manejar, se pueden usar parámetros de longitud variable; esto hace que la invocación del método sea más sencilla, sin necesidad de crear arrays manualmente.new T[]{…}

Dirección original: https://blog.csdn.net/qiuchengjia/article/details/52910888