Викликання методу Java varargs з одним нульовим аргументом?


97

Якщо у мене є метод vararg Java, foo(Object ...arg)і я телефоную foo(null, null), я маю обидва arg[0]і arg[1]як nulls. Але якщо я зателефоную foo(null), argсам по собі нуль. Чому це відбувається?

Як я повинен називати fooтакий, який foo.length == 1 && foo[0] == nullє true?

Відповіді:


95

Проблема в тому, що коли ви використовуєте літерал null, Java не знає, якого типу він повинен бути. Це може бути нульовий об’єкт, або це може бути нульовий масив об’єкта. Для одного аргументу він передбачає останній.

У вас є два варіанти. Віддайте нуль явно 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' має значення null
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' має значення null
sendNullWithCastToArray (): отриманий 'x' має значення null
sendOneValue (): отриманий 'x' - це масив розміром 1
sendThreeValues ​​(): отриманий 'x' - це масив розміром 3
sendArray (): отриманий 'x' - це масив розміром 3

це враховує також виклик варіадичної функції без параметра
беббо

3

Це тому, що метод varargs можна викликати з фактичним масивом, а не з низкою елементів масиву. Коли ви надаєте йому неоднозначне nullсаме по собі, воно припускає, що nullє Object[]. Кастинг nullTo Objectбуде це виправити.


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. Перша фаза виконує дозвіл на перевантаження, не дозволяючи перетворення боксу чи розпакування, або використання методу змінної arity. Якщо на цій фазі не знайдено жодного застосовного методу, то обробка продовжується до другої фази.

    Це гарантує, що будь-які дзвінки, які були дійсними в мові програмування Java до Java SE 5.0, не вважатимуться двозначними в результаті впровадження методів змінної arity, неявного боксу та / або розпакування. Однак оголошення методу змінної сутності (§8.4.1) може змінити метод, обраний для виразу виклику методу даного методу, оскільки метод змінної сутності на першій фазі розглядається як метод фіксованої сутності. Наприклад, оголошення m (Object ...) у класі, який вже оголошує m (Object), призводить до того, що m (Object) більше не вибирається для деяких виразів виклику (наприклад, m (null)), як m (Object [] ) є більш конкретним.

  2. Друга фаза виконує роздільну здатність перевантаження, дозволяючи бокс і розпаковування, але все ще виключає використання методу змінної arity. Якщо на цій фазі не знайдено жодного застосовного методу, то обробка продовжується до третьої фази.

    Це гарантує, що метод ніколи не вибирається за допомогою виклику методу змінної arity, якщо він застосовується за допомогою виклику методу фіксованої arity.

  3. Третя фаза дозволяє поєднувати перевантаження з методами змінної артерії, боксу та розпакування.

foo(null)матчі foo(Object... arg)з arg = nullна першій фазі. arg[0] = nullбуде третьою фазою, яка ніколи не відбувається.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.