Chiamare il metodo Java varargs con un singolo argomento null?


97

Se ho un metodo Java vararg foo(Object ...arg)e chiamo foo(null, null), ho sia arg[0]e arg[1]come nulls. Ma se chiamo foo(null), esso argstesso è nullo. Perché sta succedendo?

Come dovrei chiamare footale che foo.length == 1 && foo[0] == nullè true?

Risposte:


95

Il problema è che quando usi il valore letterale null, Java non sa che tipo dovrebbe essere. Potrebbe essere un oggetto null o un array di oggetti null. Per un singolo argomento assume quest'ultimo.

Hai due scelte. Esegui il cast del null in modo esplicito su Object o chiama il metodo utilizzando una variabile fortemente tipizzata. Guarda l'esempio di seguito:

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

Produzione:

foo called, args: [a, b, c]
foo called, args: [null, null]
foo called, args: [null]
foo called, args: [null]

Sarebbe utile ad altri se tu avessi elencato il asListmetodo nel campione e il suo scopo.
Arun Kumar

5
@ArunKumar asList()è un'importazione statica dalla java.util.Arraysclasse. Ho solo pensato che fosse ovvio. Anche se ora che ci sto pensando, probabilmente avrei dovuto usarlo Arrays.toString()poiché l'unico motivo per cui viene convertito in un elenco è che verrà stampato abbastanza.
Mike Deck

23

Hai bisogno di un cast esplicito per Object:

foo((Object) null);

Altrimenti si presume che l'argomento sia l'intero array rappresentato dai vararg.


Non proprio, guarda il mio post.
Buhake Sindi

Ops, lo stesso qui ... scusa, olio di mezzanotte.
Buhake Sindi

6

Un caso di prova per illustrare questo:

Il codice Java con una dichiarazione del metodo che accetta vararg (che sembra essere statica):

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

Questo codice Java (un test case JUnit4) chiama quanto sopra (stiamo usando il test case non per testare nulla, solo per generare un output):

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

Eseguendolo come un test JUnit si ottiene:

sendNothing (): ha ricevuto 'x' è un array di dimensione 0
sendNullWithNoCast (): la "x" ricevuta è nullo
sendNullWithCastToString (): ha ricevuto 'x' è un array di dimensione 1
sendNullWithCastToArray (): la "x" ricevuta è nullo
sendOneValue (): ricevuto 'x' è un array di dimensione 1
sendThreeValues ​​(): ricevuto 'x' è un array di dimensione 3
sendArray (): ricevuto 'x' è un array di dimensione 3

Per renderlo più interessante, chiamiamo la receive()funzione da Groovy 2.1.2 e vediamo cosa succede. Si scopre che i risultati non sono gli stessi! Questo potrebbe essere un bug però.

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

}

Eseguendolo come un test JUnit si ottiene quanto segue, con la differenza con Java evidenziata in grassetto.

sendNothing (): ha ricevuto 'x' è un array di dimensione 0
sendNullWithNoCast (): la "x" ricevuta è nullo
sendNullWithCastToString (): la "x" ricevuta è nullo
sendNullWithCastToArray (): la "x" ricevuta è nullo
sendOneValue (): ricevuto 'x' è un array di dimensione 1
sendThreeValues ​​(): ricevuto 'x' è un array di dimensione 3
sendArray (): ricevuto 'x' è un array di dimensione 3

questo considera anche la chiamata alla funzione variadica senza parametro
bebbo

3

Questo perché un metodo varargs può essere chiamato con un array effettivo piuttosto che con una serie di elementi dell'array. Quando gli fornisci l'ambiguo nullda solo, presume che nullsia un file Object[]. Lanciare il nulla Objectrisolverà questo problema.


1

preferisco

foo(new Object[0]);

per evitare eccezioni relative al puntatore null.

Spero che sia d'aiuto.


14
Bene, se è un metodo vararg, perché non chiamarlo semplicemente come foo()?
BrainStorm.exe

@ BrainStorm.exe la tua risposta dovrebbe essere contrassegnata come risposta. Grazie.
MDP

1

L'ordine per la risoluzione del sovraccarico del metodo è ( https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2 ):

  1. La prima fase esegue la risoluzione del sovraccarico senza consentire la conversione di boxing o unboxing o l'uso di invocazione del metodo arity variabile. Se durante questa fase non viene trovato alcun metodo applicabile, l'elaborazione continua alla seconda fase.

    Ciò garantisce che tutte le chiamate che erano valide nel linguaggio di programmazione Java prima di Java SE 5.0 non siano considerate ambigue come risultato dell'introduzione di metodi arity variabili, boxing implicito e / o unboxing. Tuttavia, la dichiarazione di un metodo arity variabile (§8.4.1) può cambiare il metodo scelto per un'espressione di invocazione di un metodo dato, poiché un metodo arity variabile è trattato come un metodo arity fisso nella prima fase. Ad esempio, la dichiarazione di m (Object ...) in una classe che dichiara già m (Object) fa sì che m (Object) non venga più scelto per alcune espressioni di invocazione (come m (null)), come m (Object [] ) è più specifico.

  2. La seconda fase esegue la risoluzione del sovraccarico consentendo il boxing e unboxing, ma preclude comunque l'uso dell'invocazione del metodo arity variabile. Se durante questa fase non viene trovato alcun metodo applicabile, l'elaborazione prosegue fino alla terza fase.

    Ciò garantisce che un metodo non venga mai scelto tramite il richiamo del metodo arity variabile se è applicabile tramite il richiamo del metodo arity fisso.

  3. La terza fase consente di combinare il sovraccarico con metodi variabili di arità, boxe e unboxing.

foo(null)partite foo(Object... arg)con arg = nullnella prima fase. arg[0] = nullsarebbe la terza fase, che non accade mai.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.