Varargs - aplicação prática

Pessoal,

Alguém poderia me dar um exemplo de aplicação prática de varargs?

Digo isso porque não vejo muita diferença disso:

public metodo (int... valores) { }

pra isso:

public metodo (int[] valores) { }

é uma questão de semântica da linguagem (ficar mais legível) ou há uma vantagem que eu não estou enxergando?

Isso na verdade foi muito discutido antes da sua adoção na linguagem.
Como você sabe, varargs na verdade é uma abreviação de uma passagem de um parâmetro array. Por exemplo:

System.out.printf ("%d %s", 10, "Hello");

é uma abreviação de

System.out.printf ("%d %s", new Object[]{ 10, "Hello"});

Havia gente que propunha não haver varargs (porque eles acarretam algumas ambiguidades sintáticas que são difíceis de entender). Em seu lugar essas pessoas propunham que o inicializador de arrays (new Object[]{}) pudesse ser abreviado para {}. Por exemplo:

System.out.printf ("%d %s", {10, "Hello"});

Só que foram voto vencido - afinal de contas, varargs permitem que você construa algo parecido com o printf, e como você deve saber é difícil para alguém que programou em C alguma vez na vida deixar de usar o printf.

entendi…

nesse caso seria algo mais “cosmético” por assim dizer né?

Isso é o que o pessoal chama de “syntatic sugar” (açúcar sintático).

Eu confesso que uso bastante, já que muitas vezes eu preciso passar um array de objetos ou strings, e tenho preguiça de usar “new String[]{}” ou “new Object[]{}”.

Só que varargs está mais para “syntatic sweetener” (adoçante sintático) porque ele pode ter um sabor amargo às vezes. Por exemplo, quando você faz isto:

System.out.printf ("%s", null);

o que você está fazendo?

a) Estou passando um array, cujo valor é new Object[]{null}
b) Estou passando o valor null .

Vou checar aqui na JLS o que foi definido nesse caso.

eu acho que nesse caso é: Object[]{null} não?

já que essa sintaxe é um atalho pra um array…

No Eclipse, eu fiz o seguinte teste:

01 class VarargsTest{ 02 public static void m(String ...strings) { 03 System.out.println("" + strings); 04 } 05 06 public static void main(String[] args) { 07 m(null); 08 m((String[])null); 09 m((String)null); 10 } 11 }
Na linha 7, o Eclipse dá o seguinte warning:

Quando você passa somente null, ele encara que vc está passando null mesmo. Se vc quer mandar um elemento String null, você deve dar um cast no null para String.
Mas assim, essas verificações são empíricas… Vai saber se isso é coisa da JLS ou se é só do Eclipse mesmo…

Bom, ele dá um warning e passa “null”, não “new Object[]{null}”. Que legal :frowning:

class TesteVarargs {
    public void teste (Object... args) {
        if (args == null) System.out.println ("null!");
        else if (args instanceof Object[]) {
            System.out.println ("Object[] com " + args.length + " elemento(s)");
            for (Object obj : args) {
                System.out.println ("\t[" + obj + "]");
            }
        }
    }
    
    public static void main(String[] args) {
        TesteVarargs tv = new TesteVarargs();
/*
A seguinte linha provocou um warning, pois a chamada é ambígua.
TesteVarargs.java:22: warning: non-varargs call of varargs method with inexact ar
gument type for last parameter;
cast to java.lang.Object for a varargs call
cast to java.lang.Object[] for a non-varargs call and to suppress this warning
        tv.teste (null);
*/        
        tv.teste (null); // imprime "null!"
        tv.teste();       // imprime "Object[] com 0 elemento(s)"
        tv.teste (1);     // imprime "Object[] com 1 elemento(s)" "[1]"
        tv.teste((Object) null); // imprime "Object[] com 1 elemento(s)" "[null]"
        tv.teste(new Object[]{null}); // imprime "Object[] com 1 elemento(s)" "[null]"
        tv.teste((Object[]) null); // imprime "null!"
    }
}

seguir a lógica mesmo… nada né? =:/

bom com certeza isso é uma questão da prova de certificação né? então niguém erra mais ela hehehehe

Mantu, você que sabe ler língua de advogado, perdão, definições formais de linguagens, deve estar entendendo aqui que:

(Só para lhe ajudar, "Variable arity" = varargs)

Digamos que apareça apenas um argumento. Se ele for "assignment compatible" com o tipo T[] - no seu exemplo, T = String, pois você declarou "String… strings", então ele é passado diretamente, em vez de ser encapsulado em um new T[]{} . Portanto, você poderia usar:
m (new String[] {"a", "b", "c"}) ou m ("a", "b", "c"). Ambas as formas são equivalentes.

O problema é que a constante "null" é "assignment compatible" com qualquer tipo, se você olhar na JLS em algum outro lugar. Portanto você teria de passar null e não new String[]{null}.

Na JLS não se diz que é recomendado emitir um warning no caso do valor "null". Mas como esse caso é realmente difícil de entender, tanto o Eclipse quanto o javac emitem esse warning.

[quote=thingol]Mantu, você que sabe ler língua de advogado, perdão, definições formais de linguagens, deve estar entendendo aqui que:

(Só para lhe ajudar, "Variable arity" = varargs)

[/quote]
Língua de advogado? Por quê?
Língua de perdão: [color=blue]Eita memória boa da muléstia!!![/color]
Definições formais de linguagens? Quem me dera…
Aridade (Arity): A grosso modo, é a quantidade de parâmetros de um método (linguagens imperativas), de uma função(linguagens funcionais, tipo Lisp), ou de uma regra(linguagens ???, tipo Prolog). Aprendi isso graças a [color=blue]uma excelente professora na facul[/color]

Estou dando uma lida na parte da JSL que trata sobre como o Java deve avaliar invocações de métodos… Até agora, acho que tem algo a ver com o fato dele especificar que o método mais específico é o que deve ser escolhido para a execução… mas ainda estou lendo…

Eu digo que é “linguagem de advogado” porque definições de linguagens se assemelham àquelas letras miúdas que aparecem em contratos. Só eles conseguem entender essas coisas, e olhe lá.
E isso que a JLS contém alguns exemplos, para ajudar a clarificar o que está escrito formalmente mas não está escrito muito claramente.

Bom, é meio complicado juntar as pecinhas das especificações da JLS (Java Language Specification), então posso ter comido bola em algum ponto… :?
O que eu consegui “montar” foi mais ou menos isso:
.

Isso nos diz, basicamente, que o null pode ser atribuido a qualquer tipo de referência.
.

Parâmetro formal é o parâmetro da declaração do método. Argumento é a expressão passada por parâmetro quando se invoca um método. Esta expressão, quando resolvida, deve retornar um valor de tipo compatível com o parâmetro formal correspondente.
O que a 8.4.1 nos diz é que:
:arrow: Se o último parâmetro formal de um método é um parâmetro de aridade variável de um determinado tipo T, este parâmetro formal será tratado como se fosse um vetor de T.
:arrow: Verificada a flexinha acima, dizemos que o método é um método de aridade variável.
:arrow: Todos os argumentos que não correspondem aos parâmetros formais que antecedem o parâmetro de aridade variável, serão resolvidos e seus respectivos retornos serão armazenados em um vetor o qual será passado para a invocação do método. Esta parte que parece meio confusa, contraditória até, como veremos mais adiante…
.
A JLS 15 trata de como resolver expressões
A JLS 15.12 trata de como resolver expressões que são invocação de método
A JLS 15.12.2 trata de como determinar qual a assinatura de método “casa” com uma determinada invocação de método. Esse tópico apresenta alguns subtópicos que representam as fases que se deve percorrer para encontrar a assintura que se aplica à tal invocação. A impressão trivial que se tem é que estas fases têm uma ordem a ser obedecida pela jvm.
A JLS 15.12.2.3 é uma das fases mencionadas acima (a terceira), e ela trata, basicamente, de verificar se o método invocado “casa” em termos de aridade com algum dos métodos da classe (Classe do objeto que está invocando o método). Neste tópico, resumidamente, ele diz o seguinte:
:arrow: Suponha que m é um método potencialmente aplicável(ver JLS 15.12.2.1) ao método invocado.
:arrow: Suponha também que e_1, …, e_n são os argumentos da método invocado.
:arrow: Suponha que A_i, onde 1<=i<=n, seja o tipo de e_i
:arrow: Suponha que S_1, …, S_n, sejam os tipos dos n parâmetros formais de m
Dadas as suposições acima, o método m será considerado, por assim dizer, aplicável ao método invocado se, e somente se:
:arrow: Para 1<=i<=n, o tipo do argumento e_i - que é A_i - pode ser convertido para S_i
:arrow: Uma condição doida que se aplica caso m seja um método genérico. Para o caso deste tópico aqui do GUJ, não é - aparentemente - relevante…
Se as condições acima não forem satisfeitas, a busca pelo método aplicável vai para a fase seguinte, que é especificada pela JLS 15.12.2.4.
A JLS 15.12.2.4 é a que verifica se o método invocado “casa” com algum método de aridade variável. Como creio que a explicação pra aquela problemática do null citada nos posts anteriores reside na JLS 15.12.2.3, não vou explanar esta JSL 15.12.2.4. Basta sabermos que esta é “checada” depois daquela.
.
Bom, Tendo em mente que:
:arrow: null pode ser atribuído a qualquer referência (JLS 5.2), e :arrow: Um parâmetro de aridade variável do tipo T de um método de aridade variável é tratado como um vetor do tipo T (JLS 8.4.1)
Se temos um método de aridade variável, por exemplo, declarado como segue:

public void foo(int i, bool b, String...strs){
   //...
}

E uma invocação deste método da seguinte forma:

foo(123, true, null);

Uma JVM que implementa corretamente (!) o que reza a JLS 15.12.2.3, não precisará executar as instruções especificadas pela JLS 15.12.2.4.
Esta afirmação se justifica nos termos da JLS 15.12.2.3, a qual diz, em termos resumidos, que a invocação “casa” com um determinado método m se este tem uma correspondência de aridade com a aridade da invocação.
Isto de fato acontece no caso acima descrito! Comparemos os argumentos da invocação com os parâmetros formais do método foo

Parâmetro	Tipo(Argumento) 	Tipo(Formal)	Casa?
1o.		int			int		Sim
2o.		boolean			boolean		Sim
3o.		null			String[]	Sim(Segundo JLS 5.2)

Esse é o motivo pelo qual as JVMs mais conhecidas não tratam a invocação acima como algo do tipo

foo(123, true, new String[]{null});

Pois a resolução desta instrução se encaixa na JLS 15.12.2.3 antes mesmo de passar por uma JLS que especifique a verificação de se o método é de aridade variável.
Se não há protestos… No further questions, your honor…

Pois é, seu Mantu, é por isso que mesmo os caras que efetuam a manutenção do compilador (o Peter van der Ahé no caso do Javac, não sei quem no caso do Eclipse Compiler) se embananam um pouco para entender a JLS.
Pelo menos ambos os compiladores emitem um warning no caso de varargs, quando a especificação diz uma coisa que não é intuitiva, como é o caso de passar apenas um parâmetro “null”.

varargs pode ser bastante útil pra tornar mais legível algumas implementações. Por exemplo, normalmente quando uso DAO genérico + Hibernate, eu crio um método como o abaixo:

@SuppressWarnings("unchecked") protected T findUniqueByCriteria(Criterion... criterion) { Criteria crit = getSession().createCriteria(getPersistentClass()); for (Criterion c : criterion) { crit.add(c); } return crit.uniqueResult(); }

Facilitando a escrita de outros métodos que usem Criteria.

public User search(String login, String password) { return findUniqueByCriteria(Restrictions.eq("login", login), Restrictions.eq("password", password)); }

Valeu Plentz!

era exatamente esse tipo de exemplo que eu queria.

Claro que isso não desmerece em nenhum momento thingol e Mantu que participaram da thread, escovar bits nunca é demais :wink: