¿Llamando al método Java varargs con un solo argumento nulo?

97

Si tengo un método Java vararg foo(Object ...arg)y llamo foo(null, null), tengo ambos arg[0]y arg[1]as null. Pero si llamo foo(null), en argsí mismo es nulo. ¿Por qué está pasando esto?

¿Cómo debo llamar foode tal manera que foo.length == 1 && foo[0] == nulles true?

pathikrit
fuente

Respuestas:

95

El problema es que cuando usa el nulo literal, Java no sabe qué tipo se supone que es. Podría ser un objeto nulo o podría ser una matriz de objetos nula. Para un solo argumento, asume lo último.

Tienes dos opciones. Convierta el nulo explícitamente en Object o llame al método utilizando una variable fuertemente tipada. Vea el ejemplo a continuación:

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

Salida:

foo called, args: [a, b, c]
foo called, args: [null, null]
foo called, args: [null]
foo called, args: [null]
Mike Deck
fuente
Sería útil para otros si hubiera incluido el asListmétodo en la muestra y su propósito.
Arun Kumar
5
@ArunKumar asList()es una importación estática de la java.util.Arraysclase. Simplemente asumí que era obvio. Aunque ahora que lo estoy pensando, probablemente debería haberlo usado, Arrays.toString()ya que la única razón por la que se convierte en una Lista es para que se imprima de forma bonita.
Mike Deck
23

Necesitas un elenco explícito para Object:

foo((Object) null);

De lo contrario, se supone que el argumento es la matriz completa que representan los varargs.

Bozho
fuente
En realidad no, mira mi publicación.
Buhake Sindi
Vaya, lo mismo aquí ... lo siento, aceite de medianoche.
Buhake Sindi
6

Un caso de prueba para ilustrar esto:

El código Java con una declaración de método de toma de vararg (que resulta ser estático):

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

Este código Java (un caso de prueba JUnit4) llama a lo anterior (estamos usando el caso de prueba no para probar nada, solo para generar alguna salida):

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

Ejecutar esto como una prueba JUnit produce:

sendNothing (): la 'x' recibida es una matriz de tamaño 0
sendNullWithNoCast (): la 'x' recibida es nula
sendNullWithCastToString (): recibida 'x' es una matriz de tamaño 1
sendNullWithCastToArray (): 'x' recibido es nulo
sendOneValue (): 'x' recibido es una matriz de tamaño 1
sendThreeValues ​​(): 'x' recibido es una matriz de tamaño 3
sendArray (): la 'x' recibida es una matriz de tamaño 3

Para hacer esto más interesante, llamemos a la receive()función de Groovy 2.1.2 y veamos qué sucede. ¡Resulta que los resultados no son los mismos! Sin embargo, esto puede ser un error.

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

}

Ejecutar esto como una prueba JUnit produce lo siguiente, con la diferencia con Java resaltada en negrita.

sendNothing (): la 'x' recibida es una matriz de tamaño 0
sendNullWithNoCast (): la 'x' recibida es nula
sendNullWithCastToString (): 'x' recibido es nulo
sendNullWithCastToArray (): 'x' recibido es nulo
sendOneValue (): 'x' recibido es una matriz de tamaño 1
sendThreeValues ​​(): 'x' recibido es una matriz de tamaño 3
sendArray (): la 'x' recibida es una matriz de tamaño 3
David Tonhofer
fuente
esto también considera llamar a la función variadic sin parámetro
bebbo
3

Esto se debe a que se puede llamar a un método varargs con una matriz real en lugar de una serie de elementos de la matriz. Cuando le proporciona lo ambiguo nullpor sí mismo, asume que nulles un Object[]. Lanzar el nullto Objectsolucionará esto.

ColinD
fuente
1

yo prefiero

foo(new Object[0]);

para evitar excepciones de puntero nulo.

Espero eso ayude.

Deepak Garg
fuente
14
Bueno, si es un método vararg, ¿por qué no llamarlo simplemente como foo()?
BrainStorm.exe
@ BrainStorm.exe su respuesta debe marcarse como la respuesta. Gracias.
MDP
1

El orden para la resolución de sobrecarga de métodos es ( https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2 ):

  1. La primera fase realiza la resolución de sobrecarga sin permitir la conversión de boxeo o unboxing, o el uso de la invocación del método de aridad variable. Si no se encuentra ningún método aplicable durante esta fase, el procesamiento continúa a la segunda fase.

    Esto garantiza que las llamadas que eran válidas en el lenguaje de programación Java antes de Java SE 5.0 no se consideran ambiguas como resultado de la introducción de métodos de aridad variable, boxing implícito y / o unboxing. Sin embargo, la declaración de un método de aridad variable (§8.4.1) puede cambiar el método elegido para una expresión de invocación de método de método dada, porque un método de aridad variable se trata como un método de aridad fija en la primera fase. Por ejemplo, declarar m (Objeto ...) en una clase que ya declara m (Objeto) hace que m (Objeto) ya no se elija para algunas expresiones de invocación (como m (nulo)), como m (Objeto [] ) es más específico.

  2. La segunda fase realiza la resolución de la sobrecarga al tiempo que permite el boxeo y el desempaquetado, pero aún excluye el uso de la invocación del método de aridad variable. Si no se encuentra ningún método aplicable durante esta fase, el procesamiento continúa hasta la tercera fase.

    Esto asegura que nunca se elija un método mediante la invocación del método de aridad variable si es aplicable a través de la invocación del método de aridad fija.

  3. La tercera fase permite combinar la sobrecarga con métodos de aridad variable, boxeo y unboxing.

foo(null)coincide foo(Object... arg)con arg = nullen la primera fase. arg[0] = nullsería la tercera fase, que nunca ocurre.

Alexey Romanov
fuente