Вызов метода Java varargs с одним нулевым аргументом?

97

Если у меня есть Java-метод vararg foo(Object ...arg)и я вызываю foo(null, null), у меня есть оба arg[0]и arg[1]как nulls. Но если я позвоню foo(null), argсамо будет null. Почему это происходит?

Как мне называть fooтакое, что foo.length == 1 && foo[0] == nullесть true?

патикрит
источник

Ответы:

95

Проблема в том, что когда вы используете буквальный ноль, Java не знает, какого типа он должен быть. Это может быть нулевой объект или массив пустых объектов. Для единственного аргумента предполагается второе.

У вас есть два варианта. Явно приведите значение null к Object или вызовите метод с помощью строго типизированной переменной. См. Пример ниже:

public class Temp{
   public static void main(String[] args){
      foo("a", "b", "c");
      foo(null, null);
      foo((Object)null);
      Object bar = null;
      foo(bar);
   }

   private static void foo(Object...args) {
      System.out.println("foo called, args: " + asList(args));
   }
}

Вывод:

foo called, args: [a, b, c]
foo called, args: [null, null]
foo called, args: [null]
foo called, args: [null]
Майк Палуба
источник
Было бы полезно для других, если бы вы указали asListметод в образце и его цель.
Арун Кумар
5
@ArunKumar asList()- статический импорт из java.util.Arraysкласса. Я просто решил, что это очевидно. Хотя теперь, когда я думаю об этом, мне, вероятно, следовало просто использовать, Arrays.toString()поскольку единственная причина, по которой он преобразуется в список, - это то, что он будет печатать красиво.
Mike Deck
23

Вам нужно явное приведение к Object:

foo((Object) null);

В противном случае предполагается, что аргумент представляет собой весь массив, который представляет varargs.

Божо
источник
Не совсем, смотрите мой пост.
Buhake Sindi,
Ой, здесь то же самое ... извини, полуночное масло.
Buhake Sindi,
6

Тестовый пример, чтобы проиллюстрировать это:

Код Java с объявлением метода приема vararg (который оказывается статическим):

public class JavaReceiver {
    public static String receive(String... x) {
        String res = ((x == null) ? "null" : ("an array of size " + x.length));
        return "received 'x' is " + res;
    }
}

Этот код Java (тестовый пример JUnit4) вызывает вышеизложенное (мы используем тестовый пример, чтобы ничего не тестировать, а просто для генерации некоторого вывода):

import org.junit.Test;

public class JavaSender {

    @Test
    public void sendNothing() {
        System.out.println("sendNothing(): " + JavaReceiver.receive());
    }

    @Test
    public void sendNullWithNoCast() {
        System.out.println("sendNullWithNoCast(): " + JavaReceiver.receive(null));
    }

    @Test
    public void sendNullWithCastToString() {
        System.out.println("sendNullWithCastToString(): " + JavaReceiver.receive((String)null));
    }

    @Test
    public void sendNullWithCastToArray() {
        System.out.println("sendNullWithCastToArray(): " + JavaReceiver.receive((String[])null));
    }

    @Test
    public void sendOneValue() {
        System.out.println("sendOneValue(): " + JavaReceiver.receive("a"));
    }

    @Test
    public void sendThreeValues() {
        System.out.println("sendThreeValues(): " + JavaReceiver.receive("a", "b", "c"));
    }

    @Test
    public void sendArray() {
        System.out.println("sendArray(): " + JavaReceiver.receive(new String[]{"a", "b", "c"}));
    }
}

Выполнение этого как теста JUnit дает:

sendNothing (): полученный 'x' - это массив размером 0
sendNullWithNoCast (): полученный 'x' равен нулю
sendNullWithCastToString (): полученный 'x' - это массив размером 1
sendNullWithCastToArray (): полученный 'x' равен нулю
sendOneValue (): полученный 'x' - это массив размером 1
sendThreeValues ​​(): полученный 'x' - это массив размером 3
sendArray (): полученный 'x' - это массив размером 3

Чтобы сделать это более интересным, давайте вызовем receive()функцию из Groovy 2.1.2 и посмотрим, что произойдет. Оказывается, результаты разные! Хотя это может быть ошибкой.

import org.junit.Test

class GroovySender {

    @Test
    void sendNothing() {
        System.out << "sendNothing(): " << JavaReceiver.receive() << "\n"
    }

    @Test
    void sendNullWithNoCast() {
        System.out << "sendNullWithNoCast(): " << JavaReceiver.receive(null) << "\n"
    }

    @Test
    void sendNullWithCastToString() {
        System.out << "sendNullWithCastToString(): " << JavaReceiver.receive((String)null) << "\n"
    }

    @Test
    void sendNullWithCastToArray() {
        System.out << "sendNullWithCastToArray(): " << JavaReceiver.receive((String[])null) << "\n"
    }

    @Test
    void sendOneValue() {
        System.out << "sendOneValue(): " + JavaReceiver.receive("a") << "\n"
    }

    @Test
    void sendThreeValues() {
        System.out << "sendThreeValues(): " + JavaReceiver.receive("a", "b", "c") << "\n"
    }

    @Test
    void sendArray() {
        System.out << "sendArray(): " + JavaReceiver.receive( ["a", "b", "c"] as String[] ) << "\n"
    }

}

Выполнение этого как теста JUnit дает следующее, с отличием от Java, выделенным жирным шрифтом.

sendNothing (): полученный 'x' - это массив размером 0
sendNullWithNoCast (): полученный 'x' равен нулю
sendNullWithCastToString (): полученный 'x' равен нулю
sendNullWithCastToArray (): полученный 'x' равен нулю
sendOneValue (): полученный 'x' - это массив размером 1
sendThreeValues ​​(): полученный 'x' - это массив размером 3
sendArray (): полученный 'x' - это массив размером 3
Дэвид Тонхофер
источник
это также учитывает вызов вариативной функции без параметра
bebbo
3

Это связано с тем, что метод varargs может быть вызван с реальным массивом, а не с серией элементов массива. Когда вы предоставляете ему неоднозначное nullсамо по себе, он предполагает, что nullэто файл Object[]. Кастинг nullTo Objectбудет это исправить.

ColinD
источник
1

я предпочитаю

foo(new Object[0]);

чтобы избежать исключений нулевого указателя.

Надеюсь, поможет.

Дипак Гарг
источник
14
Что ж, если это метод vararg, почему бы просто не назвать его как foo()?
BrainStorm.exe
@ BrainStorm.exe ваш ответ должен быть отмечен как ответ. Спасибо.
MDP
1

Порядок разрешения перегрузки метода следующий ( https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2 ):

  1. На первом этапе выполняется разрешение перегрузки без разрешения упаковывания или распаковки преобразования или использования вызова метода переменной арности. Если на этом этапе не найдено ни одного применимого метода, обработка переходит ко второму этапу.

    Это гарантирует, что любые вызовы, которые были допустимы в языке программирования Java до Java SE 5.0, не будут считаться неоднозначными в результате введения методов переменной арности, неявной упаковки и / или распаковки. Однако объявление метода переменной арности (§8.4.1) может изменить метод, выбранный для данного выражения вызова метода, потому что на первом этапе метод переменной арности обрабатывается как метод фиксированной арности. Например, объявление m (Object ...) в классе, который уже объявляет m (Object), приводит к тому, что m (Object) больше не выбирается для некоторых выражений вызова (таких как m (null)), как m (Object [] ) более конкретно.

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

    Это гарантирует, что метод никогда не будет выбран через вызов метода переменной арности, если он применим через вызов метода фиксированной арности.

  3. Третья фаза позволяет комбинировать перегрузку с методами переменной арности, упаковкой и распаковкой.

foo(null)совпадает foo(Object... arg)с arg = nullв первой фазе. arg[0] = nullбудет третья фаза, которой никогда не бывает.

Алексей Романов
источник