Wywołanie metody varargs w Javie z pojedynczym argumentem zerowym?

97

Jeśli mam metodę vararg Java foo(Object ...arg)i wywołuję foo(null, null), mam obie arg[0]i arg[1]jako nulls. Ale jeśli dzwonię foo(null), argsamo w sobie jest zerowe. Dlaczego to się dzieje?

Jak mam nazwać to foo, co foo.length == 1 && foo[0] == nulljest true?

pathikrit
źródło

Odpowiedzi:

95

Problem polega na tym, że kiedy używasz dosłownego null, Java nie wie, jaki to powinien być typ. Może to być pusty obiekt lub pusta tablica Object. Dla jednego argumentu zakłada to drugie.

Masz dwie możliwości. Przerzuć wartość null jawnie na Object lub wywołaj metodę przy użyciu zmiennej o jednoznacznie określonym typie. Zobacz poniższy przykład:

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));
   }
}

Wynik:

foo called, args: [a, b, c]
foo called, args: [null, null]
foo called, args: [null]
foo called, args: [null]
Mike Deck
źródło
Byłoby pomocne dla innych, gdybyś wymienił asListmetodę w próbce i jej cel.
Arun Kumar
5
@ArunKumar asList()to statyczny import z java.util.Arraysklasy. Po prostu założyłem, że to oczywiste. Chociaż teraz, kiedy o tym myślę, prawdopodobnie powinienem był po prostu użyć, Arrays.toString()ponieważ jedynym powodem, dla którego zostanie przekonwertowany na listę, jest to, że będzie ładnie drukować.
Mike Deck
24

Potrzebujesz wyraźnej obsady, aby Object:

foo((Object) null);

W przeciwnym razie przyjmuje się, że argumentem jest cała tablica, którą reprezentuje varargs.

Bozho
źródło
Nie bardzo, zobacz mój post.
Buhake Sindi
Ups, to samo tutaj ... przepraszam, olej o północy.
Buhake Sindi
6

Przypadek testowy ilustrujący to:

Kod Javy z deklaracją metody pobierającej vararg (która jest statyczna):

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;
    }
}

Ten kod Java (przypadek testowy JUnit4) wywołuje powyższe (używamy przypadku testowego, aby niczego nie testować, tylko po to, aby wygenerować dane wyjściowe):

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"}));
    }
}

Uruchomienie tego jako testu JUnit daje:

sendNothing (): otrzymane 'x' jest tablicą o rozmiarze 0
sendNullWithNoCast (): otrzymane 'x' jest null
sendNullWithCastToString (): otrzymano 'x' jest tablicą o rozmiarze 1
sendNullWithCastToArray (): odebrane „x” ma wartość null
sendOneValue (): otrzymane 'x' jest tablicą o rozmiarze 1
sendThreeValues ​​(): otrzymane 'x' jest tablicą o rozmiarze 3
sendArray (): odebrane 'x' to tablica o rozmiarze 3

Aby było to bardziej interesujące, wywołajmy receive()funkcję z Groovy 2.1.2 i zobaczmy, co się stanie. Okazuje się, że wyniki nie są takie same! Może to być jednak błąd.

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"
    }

}

Uruchomienie tego jako testu JUnit daje następujące wyniki, z różnicą w stosunku do Java wyróżnioną pogrubioną czcionką.

sendNothing (): otrzymane 'x' jest tablicą o rozmiarze 0
sendNullWithNoCast (): otrzymane 'x' jest null
sendNullWithCastToString (): odebrane „x” ma wartość null
sendNullWithCastToArray (): odebrane „x” ma wartość null
sendOneValue (): otrzymane 'x' jest tablicą o rozmiarze 1
sendThreeValues ​​(): otrzymane 'x' jest tablicą o rozmiarze 3
sendArray (): odebrane 'x' to tablica o rozmiarze 3
David Tonhofer
źródło
dotyczy to również wywołania funkcji
wariadycznej
3

Dzieje się tak, ponieważ metodę varargs można wywołać z rzeczywistą tablicą, a nie serią elementów tablicy. Kiedy zapewniasz mu niejednoznaczne nullsamo w sobie, zakłada, że nulljest to plik Object[]. Przesyłanie nulldo Objectrozwiąże ten problem.

ColinD
źródło
1

wolę

foo(new Object[0]);

aby uniknąć wyjątków wskaźnika Null.

Mam nadzieję, że to pomoże.

Deepak Garg
źródło
14
Cóż, jeśli jest to metoda vararg, dlaczego nie nazwać jej tak foo()?
BrainStorm.exe,
@ BrainStorm.exe Twoja odpowiedź powinna być oznaczona jako odpowiedź. dzięki.
MDP
1

Kolejność rozwiązywania problemu przeciążenia metod jest następująca ( https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2 ):

  1. Pierwsza faza przeprowadza rozpoznanie przeciążenia bez zezwalania na konwersję typu boxing lub unboxing lub użycie wywołania metody zmiennej arity. Jeśli w tej fazie nie zostanie znaleziona odpowiednia metoda, przetwarzanie jest kontynuowane w drugiej fazie.

    Gwarantuje to, że wszelkie wywołania, które były poprawne w języku programowania Java przed Java SE 5.0, nie są uważane za niejednoznaczne w wyniku wprowadzenia metod ze zmienną aranżacją, niejawnego pakowania i / lub rozpakowywania. Jednak deklaracja metody ze zmienną aranżacją (§ 8.4.1) może zmienić metodę wybraną dla danego wyrażenia wywołania metody, ponieważ metoda ze zmienną aranżacją jest traktowana jako metoda ze stałą liczbą znaków w pierwszej fazie. Na przykład zadeklarowanie m (Object ...) w klasie, która już zadeklarowała m (Object), powoduje, że m (Object) nie jest już wybierane dla niektórych wyrażeń wywołania (takich jak m (null)), jako m (Object []) ) jest bardziej szczegółowy.

  2. Druga faza rozwiązuje problem z przeciążeniem, pozwalając jednocześnie na pakowanie i rozpakowywanie, ale nadal wyklucza użycie wywołania metody zmiennej arity. Jeśli w tej fazie nie zostanie znaleziona odpowiednia metoda, przetwarzanie jest kontynuowane do trzeciej fazy.

    Gwarantuje to, że metoda nigdy nie zostanie wybrana przez wywołanie metody zmiennej tablicy, jeśli ma ona zastosowanie za pośrednictwem wywołania metody o stałej tablicy.

  3. Trzecia faza pozwala na połączenie przeciążania z metodami o zmiennej wartości, boxingiem i unboxingiem.

foo(null)mecze foo(Object... arg)z arg = nullw pierwszej fazie. arg[0] = nullbyłaby trzecia faza, która nigdy się nie wydarzy.

Alexey Romanov
źródło