Chamando o método varargs Java com um único argumento nulo?

97

Se eu tiver um método Java vararg foo(Object ...arg)e chamar foo(null, null), tenho tanto arg[0]e arg[1]como nulls. Mas se eu ligar foo(null), argele mesmo é nulo. Por que isso está acontecendo?

Como devo chamar foode tal forma que foo.length == 1 && foo[0] == nullé true?

patikrit
fonte

Respostas:

95

O problema é que, quando você usa o nulo literal, o Java não sabe que tipo deve ser. Pode ser um objeto nulo ou pode ser uma matriz de objeto nula. Para um único argumento, ele assume o último.

Você tem duas opções. Converta o nulo explicitamente em Object ou chame o método usando uma variável fortemente tipada. Veja o exemplo abaixo:

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

Resultado:

foo called, args: [a, b, c]
foo called, args: [null, null]
foo called, args: [null]
foo called, args: [null]
Mike Deck
fonte
Seria útil para outras pessoas se você tivesse listado o asListmétodo na amostra e sua finalidade.
Arun Kumar
5
@ArunKumar asList()é uma importação estática da java.util.Arraysclasse. Eu apenas presumi que fosse óbvio. Embora agora que estou pensando sobre isso, eu provavelmente deveria ter apenas usado, Arrays.toString()pois o único motivo pelo qual ele foi convertido em uma lista é para que tenha uma impressão bonita.
Mike Deck de
23

Você precisa de um elenco explícito para Object:

foo((Object) null);

Caso contrário, o argumento é assumido como todo o array que o varargs representa.

Bozho
fonte
Na verdade não, veja meu post.
Buhake Sindi
Opa, o mesmo aqui ... desculpe, óleo da meia-noite.
Buhake Sindi
6

Um caso de teste para ilustrar isso:

O código Java com uma declaração de método vararg-taking (que por acaso é estática):

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 (um caso de teste JUnit4) chama o acima (estamos usando o caso de teste não para testar nada, apenas para gerar alguma saída):

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

Executá-lo como um teste JUnit produz:

sendNothing (): 'x' recebido é uma matriz de tamanho 0
sendNullWithNoCast (): 'x' recebido é nulo
sendNullWithCastToString (): 'x' recebido é uma matriz de tamanho 1
sendNullWithCastToArray (): 'x' recebido é nulo
sendOneValue (): recebido 'x' é uma matriz de tamanho 1
sendThreeValues ​​(): recebido 'x' é uma matriz de tamanho 3
sendArray (): o 'x' recebido é um array de tamanho 3

Para tornar isso mais interessante, vamos chamar a receive()função do Groovy 2.1.2 e ver o que acontece. Acontece que os resultados não são os mesmos! Isso pode ser um bug.

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

}

Executar isso como um teste JUnit produz o seguinte, com a diferença para Java destacada em negrito.

sendNothing (): 'x' recebido é uma matriz de tamanho 0
sendNullWithNoCast (): 'x' recebido é nulo
sendNullWithCastToString (): 'x' recebido é nulo
sendNullWithCastToArray (): 'x' recebido é nulo
sendOneValue (): recebido 'x' é uma matriz de tamanho 1
sendThreeValues ​​(): recebido 'x' é uma matriz de tamanho 3
sendArray (): o 'x' recebido é um array de tamanho 3
David Tonhofer
fonte
isto considera também chamar a função
variadic
3

Isso ocorre porque um método varargs pode ser chamado com uma matriz real em vez de uma série de elementos da matriz. Quando você fornece o ambíguo nullsozinho, ele assume que nullé um Object[]. Transmitir nullpara Objectcorrigirá isso.

ColinD
fonte
1

eu prefiro

foo(new Object[0]);

para evitar exceções de ponteiro nulo.

Espero que ajude.

Deepak Garg
fonte
14
Bem, se é um método vararg, por que não chamá-lo assim foo()?
BrainStorm.exe
@ BrainStorm.exe sua resposta deve ser marcada como a resposta. obrigado.
MDP de
1

A ordem para resolução de sobrecarga de método é ( https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2 ):

  1. A primeira fase executa a resolução de sobrecarga sem permitir a conversão de boxing ou unboxing, ou o uso de invocação de método de aridade variável. Se nenhum método aplicável for encontrado durante esta fase, o processamento continua para a segunda fase.

    Isso garante que quaisquer chamadas que eram válidas na linguagem de programação Java antes do Java SE 5.0 não sejam consideradas ambíguas como resultado da introdução de métodos de aridade variável, boxing e / ou unboxing implícito. No entanto, a declaração de um método de aridade variável (§8.4.1) pode alterar o método escolhido para uma dada expressão de invocação de método de método, porque um método de aridade variável é tratado como um método de aridade fixa na primeira fase. Por exemplo, declarar m (Object ...) em uma classe que já declara m (Object) faz com que m (Object) não seja mais escolhido para algumas expressões de invocação (como m (null)), como m (Object [] ) é mais específico.

  2. A segunda fase executa a resolução de sobrecarga enquanto permite boxing e unboxing, mas ainda impede o uso de invocação de método de aridade variável. Se nenhum método aplicável for encontrado durante esta fase, o processamento continua para a terceira fase.

    Isso garante que um método nunca seja escolhido por meio de invocação de método de aridade variável se for aplicável por meio de invocação de método de aridade fixa.

  3. A terceira fase permite que a sobrecarga seja combinada com métodos de aridade variável, boxing e unboxing.

foo(null)coincide foo(Object... arg)com arg = nullna primeira fase. arg[0] = nullseria a terceira fase, o que nunca acontece.

Alexey Romanov
fonte