В Java 5 были введены параметры переменной длины, позволяющие передавать при вызове метода неопределённое количество параметров. Параметры переменной длины — это синтаксический сахар в Java, по сути основанный на реализации массива:

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

Определение методов

При определении метода добавьте три точки после последнего формального параметра…, указывая, что этот формальный параметр может принимать несколько значений параметров, и эти несколько значений параметров передаются как массив. В приведённом выше определении есть несколько ключевых моментов, которые следует отметить:

  • Переменные параметры могут быть только последним параметром функции, но перед ними могут быть или не быть другие параметры.

  • Поскольку переменные параметры должны быть последним параметром, функция может иметь не более одного переменного параметра.

  • Переменные параметры Java преобразуются компилятором в массив.

  • После компиляции параметров переменной длины в байт-код они предстают в сигнатуре метода в виде массива. Сигнатуры этих двух методов идентичны, и их нельзя использовать как перегрузку метода. Если они присутствуют одновременно, код не скомпилируется. Параметры переменной длины совместимы с массивами, но обратное неверно.

    public void foo(String...varargs){}
    
    foo("arg1", "arg2", "arg3");
    
    //上述过程和下面的调用是等价的
    foo(new String[]{"arg1", "arg2", "arg3"});
  • В J2SE 1.5 был добавлен механизм«generics», позволяющий при определённых условиях параметризовать тип. Например, при написании класса можно использовать идентификатор (например, T) для обозначения типа формального параметра метода. Какой тип в действительности представляет этот идентификатор, указывается при создании экземпляра класса. Этот механизм обеспечивает более полное повторное использование кода и более строгую проверку типов во время компиляции. Однако механизм generics нельзя использовать вместе с формальными параметрами переменной длины. Если тип формального параметра, который может соответствовать неопределённому числу фактических аргументов, обозначен идентификатором, компилятор выдастошибку «generic array creation».

    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){}
    }

Вызов методов с переменным числом аргументов

При вызове метода с переменным числом аргументов можно передать от нуля до любого числа аргументов, и компилятор преобразует переменные аргументы в массив. Можно также передать массив напрямую, как показано в следующем примере:

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"});//直接传递数组
    }
}

Перегрузка методов

Сначала сопоставляются фиксированные параметры

При вызове перегруженного метода, если вызов может соответствовать как перегруженному методу с фиксированными параметрами, так и перегруженному методу с параметрами переменной длины, выбирается метод с фиксированными параметрами:

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

> Сопоставление нескольких переменных аргументов

При вызове перегруженного метода, если вызов может соответствовать двум перегруженным методам с параметрами переменной длины, возникает ошибка компиляции:

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

Переопределение методов

Избегайте перегрузки методов с параметрами переменной длины

Даже если компилятор может определить конкретный вызываемый метод, отдавая предпочтение фиксированным параметрам, читатели кода всё равно могут легко попасть в ловушку. Перегрузка методов с параметрами переменной длины требует тщательного обдумывания.

Не позволяйте null-значениям и пустым значениям угрожать методам с переменным числом параметров

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

Измените его следующим образом:

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

Если сообщить компилятору, что это null-значение имеет тип String, компиляция пройдёт гладко, тем самым снизится вероятность возникновения ошибок.

Переопределение методов с переменным числом параметров также должно подчиняться правилам

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

Первый вариант успешно компилируется. Почему? На самом деле объект base выполняет восходящее преобразование объекта sub подкласса, а список параметров определяется родительским классом, поэтому он естественно подходит. Теперь рассмотрим случай прямого вызова из подкласса. Компилятор видит, что подкласс переопределил метод print родительского класса, поэтому он обязательно будет использовать метод print, переопределённый подклассом. Даже если список параметров не совпадает, он не станет снова искать соответствие в родительском классе, потому что, как только совпадение найдено, поиск прекращается. Отсюда ошибка несоответствия типов.

Это особый случай. Список параметров переопределяющего метода на самом деле может отличаться от списка параметров родительского класса, что нарушает определение переопределения и может вызывать необъяснимые ошибки.

Здесь давайте обобщим условия, которым должно удовлетворять переопределение:

  • Переопределяющий метод не может сужать права доступа.

  • Список параметров должен совпадать со списком параметров переопределяемого метода (включая явную форму).

  • Возвращаемый тип должен совпадать с возвращаемым типом переопределяемого метода или быть его подклассом.

  • Переопределяющий метод не может выбрасывать новые проверяемые исключения или исключения, выходящие за пределы области родительского класса, но может выбрасывать меньше более ограниченных исключений или не выбрасывать их вовсе.


Возможные проблемы

ИспользованиеObject…в качестве параметра переменной длины:

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[]невозможно привести кObject[], поэтому он рассматривается как обычный объект-массив;Integer[]можно привести кObject[], и его можно использовать как массив объектов.


Меры предосторожности при вызове методов через рефлексию

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

Среди четырёх вызовов выше первые два вызовут исключение времени выполненияjava.lang.IllegalArgumentException: wrong number of arguments, тогда как последние два вызываются нормально.

Рефлексия получается во время выполнения. С точки зрения времени выполнения параметры переменной длины и массивы идентичны, поэтому сигнатура метода такова:

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

Также рассмотрим объявление метода объекта Method:

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

Хотя args — параметр переменной длины, его длина ограничена длиной списка параметров реального метода, представленного объектом Method. Согласно сигнатуре времени выполнения, ([Ljava/lang/String;)V фактически имеет только один формальный параметр, а именно String[] varargs. Поэтому в invoke(Object obj, Object… args) фактическая длина аргумента переменного параметра args может быть только 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,正确。上一个和这个是等价的

Когда использовать аргументы переменной длины?

Stack OverflowЕсть вопрос об использовании параметров переменной длины. Короче говоря,
Вы можете использовать параметры переменной длины, когда не уверены в количестве объектов, которые метод должен обработать. Это упрощает вызовы методов, избавляя от необходимости вручную создавать массивы.new T[]{…}

Исходный адрес: https://blog.csdn.net/qiuchengjia/article/details/52910888