Вызов метода Java varargs с одним нулевым аргументом? - PullRequest
88 голосов
/ 27 октября 2010

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

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

Ответы [ 6 ]

91 голосов
/ 27 октября 2010

Проблема в том, что когда вы используете литерал null, 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]
22 голосов
/ 27 октября 2010

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

foo((Object) null);

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

5 голосов
/ 06 мая 2013

Тестовый пример, иллюстрирующий это:

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(): received 'x' is an array of size 0
sendNullWithNoCast(): received 'x' is null
sendNullWithCastToString(): received 'x' is an array of size 1
sendNullWithCastToArray(): received 'x' is null
sendOneValue(): received 'x' is an array of size 1
sendThreeValues(): received 'x' is an array of size 3
sendArray(): received 'x' is an array of size 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(): received 'x' is an array of size 0
sendNullWithNoCast(): received 'x' is null
<b>sendNullWithCastToString(): received 'x' is null</b>
sendNullWithCastToArray(): received 'x' is null
sendOneValue(): received 'x' is an array of size 1
sendThreeValues(): received 'x' is an array of size 3
sendArray(): received 'x' is an array of size 3
3 голосов
/ 27 октября 2010

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

1 голос
/ 08 апреля 2019

Порядок разрешения перегрузки методов: (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) может изменить метод, выбранный для данного выражения вызова метода метода, поскольку метод переменной арности рассматривается какисправлен метод arity на первом этапе. Например, объявление m (Object ...) в классе, который уже объявляет m (Object), приводит к тому, что m (Object) больше не выбирается для некоторых выражений вызова (таких как m (null)), так как m (Object []) является более конкретным.

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

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

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

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

1 голос
/ 26 ноября 2013

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

foo(new Object[0]);

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

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

...