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

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

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

Ответ 1

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

Ответ 2

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

foo((Object) null);

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

Ответ 3

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

Код 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
sendNullWithCastToString(): received 'x' is null
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

Ответ 4

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

Ответ 5

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

foo(new Object[0]);

чтобы избежать исключений Null pointer.

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

Ответ 6

Порядок разрешения перегрузки методов следующий (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 будет третьей фазой, которая никогда не происходит.