При передаче единственного null в varargs-параметр что фактически получает метод: массив из одного элемента или null-ссылку?
Метод получает null-ссылку на массив, а не массив из одного null-элемента. Это происходит потому, что null может быть напрямую преобразован в тип массива varargs-параметра, поэтому Java выбирает вызов метода в формате фиксированной арности.
Чтобы передать один null-элемент, нужно явно привести значение к типу элемента, например к Object. Иначе обращение к длине массива или перебор его элементов может привести к NullPointerException.
Varargs появились в Java 5, чтобы методам можно было передавать переменное число аргументов без ручного создания массива при каждом вызове. На уровне выполнения такой параметр всё равно представлен массивом.
Для сохранения совместимости с обычными вызовами массивных параметров Java разрешает вызывать varargs-метод как с переменным числом аргументов, так и напрямую с массивом. Именно это правило становится неоднозначным для null: он подходит и как значение элемента, и как ссылка на сам массив, но приоритет получает вариант с массивом.
Разработчик может ожидать, что единственный null означает массив длиной один, содержащий null. Фактически метод получает отсутствие массива, поэтому код, который сразу использует длину массива или цикл по нему, завершится исключением.
Проблема особенно заметна в логировании, фабричных методах и API с параметрами Object...: внешне вызов выглядит корректно, но смысл переданного значения зависит от приведения типа.
Рассмотрим минимальный пример:
Первый вызов передаёт null как ссылку на массив Object[], поэтому выводит null. Второй вызов явно задаёт null как элемент: Java создаёт массив из одного элемента, и выводом будет 1. Третий вызов явно подтверждает передачу null-массива.
Причина в выборе фиксированной арности: varargs-метод сначала может быть применён как обычный метод с одним параметром типа Object[]. Значение null совместимо с этим ссылочным типом, поэтому упаковка в новый массив не требуется.
При вызове с null компилятор обычно выдаёт предупреждение о неоднозначно выглядящем varargs-вызове. Это предупреждение важно: оно сигнализирует, что передан null-массив, а не один null-элемент.
Компромисс varargs состоит в удобстве вызова против необходимости контролировать семантику null. Если API должен различать «нет аргументов», «передан null-массив» и «передан один null», лучше использовать явные перегруженные методы или документировать требуемое приведение типа.
В методе логирования log(Object... values) разработчик вызывает log(null), ожидая запись одного пустого значения. Внутри метода выполняется перебор values, но приложение получает NullPointerException, потому что массив отсутствует.
Вариант с обработкой values == null защищает метод от падения, но не устраняет смысловую неоднозначность: нельзя понять, хотел ли вызывающий передать null-массив или один null-элемент. Вариант с обязательным вызовом log((Object) null) точнее, но требует дисциплины от всех клиентов API.
Практичное решение — явно договориться о контракте: считать null-массив ошибкой либо обрабатывать его отдельно, а для одного null-элемента использовать явное приведение к типу элемента. Это делает вызов однозначным и предотвращает скрытые ошибки при рефакторинге.
Да. Если аргумент не совместим с типом самого массива, Java собирает переданные значения в новый массив нужного типа. Например, вызов с одной строкой передаст массив длиной один, содержащий эту строку.
Исключение — случай, когда единственный аргумент уже совместим с типом массива, как null или явно созданный массив. Тогда Java может передать его напрямую без упаковки в дополнительный массив.
Вызов без аргументов создаёт пустой массив длины ноль. Вызов с null, распознанным как массив, передаёт null-ссылку; у неё нет длины, и обращение к ней как к массиву вызывает NullPointerException.
Поэтому проверка values.length == 0 не заменяет проверку values == null. Если контракт допускает оба состояния, сначала нужно проверить саму ссылку.
Нет, такое приведение только явно сохраняет семантику null-массива. Чтобы получить один null-элемент, null нужно привести к типу элемента, например Object, после чего Java создаст массив из одного элемента.
Явное приведение полезно не только для устранения предупреждения компилятора, но и для фиксации намерения вызывающего кода. Это снижает риск того, что другой разработчик неверно интерпретирует вызов.