Разница между double... и double [] в объявлении формального параметра

У меня вопрос: в чем разница между этими двумя объявлениями?

 public static void printMax(double... numbers) { ... }

 public static void printmax(double numbers[])  { ... }

Является ли double... numbers тем же, что и double numbers[]?

Ответ 1

В varargs

Конструкция Type... в объявлении параметра метода обычно называется varargs. В JLS он называется параметром переменной arity.

JLS 8.4.1 Параметры формата

Последний формальный параметр в списке является особым; он может быть переменным параметром arity, обозначенным elipsis, следующим за типом.

Если последний формальный параметр является параметром переменной arty типа T, считается, что он определяет формальный параметр типа T[]. Затем метод является методом переменной arity. В противном случае это фиксированный метод arity. Вызовы метода переменной arity могут содержать более фактические выражения аргументов, чем формальные параметры. Все фактические выражения аргументов, которые не соответствуют формальным параметрам, предшествующим параметру переменной arity, будут оцениваться и результаты будут сохранены в массив, который будет передан вызову метода.

Чтобы проиллюстрировать код, это то, что позволяет сделать varargs:

static void f(int... nums) {
    for (int num : nums) {
        System.out.println(num);
    }
}
//...

f(1,2,3); // prints "1", "2", "3"

Напротив, без конструкции varargs вы должны сделать это:

static void g(int[] nums) {
    for (int num : nums) {
        System.out.println(num);
    }       
}
//...

g(new int[] { 1, 2, 3 }); // prints "1", "2", "3"

varargs - это то, что называется синтаксическим сахаром, который скрывает от вас многословие.

Итак, чтобы вернуться к вашему вопросу, разница между printMax(double... numbers) и printmax(double numbers[]) заключается в том, что первый - это метод переменной arity, то есть вы можете дать ему переменное количество параметров. Последний является фиксированным методом arity, что означает, что он примет один и только параметр.

Обратите внимание на приведенную выше цитату о T..., которая действительно является T[]. То есть даже с помощью varargs вы все равно можете сделать следующее:

f(new int[] { 1, 2, 3 }); // prints "1", "2", "3"

Здесь вы вручную создаете массив для хранения параметров vararg. Фактически, если вы переходите к декомпиляции кода, вы обнаружите, что точно так же, как указано в JLS, f действительно принимает параметр int[], а f(1, 2, 3) реализуется как f(new int[] { 1, 2, 3 }).

См. также


Varargs gotchas

Как решены varargs, это довольно сложно, и иногда это делает вещи, которые могут вас удивить.

Рассмотрим следующий пример:

static void count(Object... objs) {
    System.out.println(objs.length);
}

count(null, null, null); // prints "3"
count(null, null); // prints "2"
count(null); // throws java.lang.NullPointerException!!!

Из-за того, как решены varargs, последний оператор вызывает objs = null, что, конечно, вызовет NullPointerException с помощью objs.length. Если вы хотите дать один аргумент null параметру varargs, вы можете выполнить одно из следующих действий:

count(new Object[] { null }); // prints "1"
count((Object) null); // prints "1"

Связанные вопросы

Ниже приведен пример некоторых вопросов, которые люди задавали при работе с varargs:


Вкл., когда использовать varargs

Как показал предыдущий раздел, varargs может быть сложным. Однако в правильных ситуациях они могут привести к гораздо более сжатому коду.

Здесь цитата из Effective Java 2nd Edition, пункт 42: разумно использовать varargs (выделение автором):

Урок ясен. Не модифицируйте каждый метод с окончательным параметром массива; используйте varargs только тогда, когда вызов действительно работает с последовательностью значений переменной длины.

Мало того, что varargs путают, это также может быть дорогостоящим. Эффективная Java 2nd Edition на самом деле рекомендует предоставлять перегрузки с фиксированной архитектурой для наиболее распространенных сценариев использования.

Предположим, вы определили, что 95 процентов вызовов метода имеют три или меньше параметров. Затем объявите пять перегрузок метода, по одному для каждого с нулем через три обычных параметра, и один varargs для использования, когда число параметров превышает три.

Книга идет гораздо глубже, но по существу вам следует использовать varargs, когда это действительно имеет смысл. И даже в этих случаях вы все равно можете рассмотреть возможность предоставления перегрузок с фиксированной архитектурой по соображениям производительности.

Связанный с этим вопрос

Ссылки API

Вот несколько примеров, где varargs имеет смысл:


В объявлениях массивов

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

int x[];

Вместо этого вы должны скопировать скобки с типом, а не с идентификатором:

int[] x;

Обратите внимание, что это также то, как массивы упоминаются в приведенных выше обсуждениях, например. T[] int[] и т.д.

Связанные вопросы

Ответ 2

Основное отличие состоит в том, что в первом случае вы можете вызвать printMax с несколькими аргументами:

printMax(3.0, 4.0, 5.0)

тогда как во втором случае он принимает только один аргумент (массив double).

Внутри тела метода числа будут доступны как массив в обоих случаях.

Следует иметь в виду, что в первом случае вы все равно можете передать один массив из double, даже если используется нотация varargs.

Ответ 3

Не точно - double... означает список переменных аргументов - все аргументы, переданные в этой точке, будут упакованы вместе как один массив.

В методе может быть только один параметр vararg, и по очевидным причинам он должен быть последним в своем прототипе. Однако он будет доступен в методе как массив, поэтому в этом конкретном случае разница невелика.