Java Parâmetros e Argumentos

Na aula de Métodos, o método saudar() sempre imprimia a mesma coisa. Para ele saudar a Ana, o Bruno ou a Carla, ele precisa receber o nome de quem chamou. É isso que os parâmetros fazem: são as "entradas" de um método, e os valores que você manda na chamada são os argumentos.

Você vai aprender
  • A diferença entre parâmetro (na declaração) e argumento (na chamada).
  • Como declarar um ou vários parâmetros, cada um com seu tipo.
  • O que o javac diz quando os argumentos não combinam.
  • A passagem por valor: por que mudar um int dentro do método não muda o de fora, mas mudar um array muda.
  • Como receber "quantos valores vierem" com varargs (int... numeros).

A ideia antes do código: o formulário

Pense num formulário de matrícula. Ele tem campos com nome: "Nome do aluno: ____", "Idade: ____". O formulário em branco é sempre o mesmo; o que muda é o que cada pessoa escreve nos campos.

  • Os campos em branco do formulário são os parâmetros: fazem parte da declaração do método e dizem "aqui vai chegar um nome".
  • O que a pessoa escreve nos campos são os argumentos: os valores de verdade, enviados na chamada.

Uma frase para lembrar: o parâmetro é a vaga; o argumento é quem ocupa a vaga.


Um parâmetro

public class Main {
    static void saudar(String nome) {
        System.out.println("Olá, " + nome + "! Bem-vindo à escola.");
    }

    public static void main(String[] args) {
        saudar("Ana");
        saudar("Bruno");
        String aluna = "Carla";
        saudar(aluna);
    }
}
$ javac Main.java && java Main
Olá, Ana! Bem-vindo à escola.
Olá, Bruno! Bem-vindo à escola.
Olá, Carla! Bem-vindo à escola.
static void saudar
O começo de sempre: um método da classe, que não devolve nada, chamado saudar.
(String nome)
O parâmetro. Dentro dos parênteses você declara uma variável: o tipo (String, um texto) e o nome (nome). É igual a declarar String nome;, só que o valor vem de quem chama.
"Olá, " + nome + "!…"
Dentro do corpo, nome é uma variável comum. A cada chamada, ela vale o argumento recebido.
saudar("Ana")
A chamada com um argumento: o texto "Ana" é copiado para o parâmetro nome, e o corpo roda.
saudar(aluna)
O argumento pode ser uma variável. O método recebe o valor dela ("Carla"); o nome da variável de fora (aluna) não importa para o método.

Repare na última chamada: no main a variável se chama aluna, no método o parâmetro se chama nome. Não precisa ser o mesmo nome. Na chamada, o Java faz algo como String nome = aluna; e pronto.

ParâmetroArgumento
Onde apareceNa declaração do métodoNa chamada do método
ExemploString nome em saudar(String nome)"Ana" em saudar("Ana")
O que éUma variável, com tipo e nomeUm valor: literal, variável ou conta ("Ana" + "!")
Em inglêsparameter (às vezes "formal parameter")argument (às vezes "actual argument")
Você já usa argumentos desde a primeira aula

Em System.out.println("Olá"), o texto "Olá" é o argumento do método println. Em Math.max(3, 7), os argumentos são 3 e 7. E o String[] args do main é um parâmetro, que recebe os argumentos da linha de comando.


Vários parâmetros

Para mandar mais de uma informação, separe os parâmetros com vírgula. Cada um tem o seu tipo, mesmo que os tipos se repitam:

public class Main {
    static void mostrarNota(String aluno, double nota) {
        System.out.println(aluno + " tirou " + nota);
    }

    public static void main(String[] args) {
        mostrarNota("Bruno", 8.5);
        mostrarNota("Carla", 9);
        mostrarNota("Davi", 7.25);
    }
}
$ javac Main.java && java Main
Bruno tirou 8.5
Carla tirou 9.0       « o 9 (int) virou 9.0 (double) ao entrar no parâmetro
Davi tirou 7.25
String aluno
Primeiro parâmetro: um texto.
,
A vírgula separa um parâmetro do outro.
double nota
Segundo parâmetro: um número com casas decimais.
mostrarNota("Bruno", 8.5)
Os argumentos são distribuídos pela posição: o primeiro vai para o primeiro parâmetro, o segundo para o segundo.

Três regras que o compilador confere em toda chamada:

  1. Quantidade: o número de argumentos é igual ao de parâmetros.
  2. Ordem: os argumentos seguem a ordem dos parâmetros. Java não tem "argumentos com nome".
  3. Tipo: cada argumento combina com o tipo do parâmetro, ou pode ser convertido sem perda. Por isso o 9 (um int) foi aceito num double e virou 9.0: é a conversão automática que você viu em Conversão de Tipos. O contrário (um double num parâmetro int) é erro.
Cada parâmetro precisa do seu tipo

Na declaração de variáveis você pode escrever double a, b;. Nos parâmetros, não: static void media(double a, b) é erro. Escreva static void media(double a, double b).

Missão: complete o método mostrarPreco com dois parâmetros, String fruta e double preco, para o programa imprimir banana custa R$ 3.5 e maçã custa R$ 7.9.

public class Main {
    static void mostrarPreco() {
        System.out.println(fruta + " custa R$ " + preco);
    }

    public static void main(String[] args) {
        mostrarPreco("banana", 3.5);
        mostrarPreco("maçã", 7.9);
    }
}
public class Main {
    static void mostrarPreco(String fruta, double preco) {
        System.out.println(fruta + " custa R$ " + preco);
    }

    public static void main(String[] args) {
        mostrarPreco("banana", 3.5);
        mostrarPreco("maçã", 7.9);
    }
}

Quando os argumentos não combinam

O compilador compara a chamada com a declaração. Se algo não bate, o programa nem chega a rodar. Estes são os três casos mais comuns.

Argumentos a menos (ou a mais)

public class Main {
    static void mostrarNota(String aluno, double nota) {
        System.out.println(aluno + " tirou " + nota);
    }

    public static void main(String[] args) {
        mostrarNota("Bruno");
    }
}
$ javac Main.java
Main.java:7: error: method mostrarNota in class Main cannot be applied to given types;
        mostrarNota("Bruno");
        ^
  required: String,double
  found:    String
  reason: actual and formal argument lists differ in length
1 error
Linha da mensagemTradução
method mostrarNota in class Main cannot be applied to given types;O método mostrarNota não pode ser usado com os tipos que você passou.
required: String,doubleO que a declaração pede: um String e um double.
found: StringO que a chamada mandou: só um String.
reason: actual and formal argument lists differ in lengthMotivo: a lista de argumentos (actual) e a de parâmetros (formal) têm tamanhos diferentes.

Argumentos na ordem errada (ou do tipo errado)

$ javac Main.java
Main.java:7: error: incompatible types: double cannot be converted to String
        mostrarNota(8.5, "Bruno");
                    ^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error

A Ana trocou a ordem: mandou o número primeiro. O primeiro parâmetro é String, e um double não vira String sozinho. A setinha aponta para o argumento culpado. A nota no fim diz que a mensagem foi simplificada; com a opção -Xdiags:verbose, o javac mostra a versão completa, no mesmo formato do caso anterior:

$ javac -Xdiags:verbose Main.java
Main.java:7: error: method mostrarNota in class Main cannot be applied to given types;
        mostrarNota(8.5, "Bruno");
        ^
  required: String,double
  found:    double,String
  reason: argument mismatch; double cannot be converted to String
1 error

Parâmetro sem tipo

$ javac Main.java
Main.java:2: error: <identifier> expected
    static void saudar(nome) {
                           ^
1 error

A declaração foi static void saudar(nome). O Java leu nome como se fosse o tipo e ficou esperando o nome da variável (identifier = identificador, um nome). A correção: static void saudar(String nome).


Passagem por valor: o método recebe uma cópia

Esta é a parte mais importante (e a que mais confunde) da aula. Em Java, quando você chama um método, cada argumento é copiado para o parâmetro. O nome técnico é passagem por valor. O método trabalha com a cópia, nunca com a variável original de quem chamou.

Com tipos primitivos: o original não muda

public class Main {
    static void dobrar(int numero) {
        numero = numero * 2;
        System.out.println("Dentro do método: " + numero);
    }

    public static void main(String[] args) {
        int pontos = 5;
        dobrar(pontos);
        System.out.println("No main: " + pontos);
    }
}
$ javac Main.java && java Main
Dentro do método: 10
No main: 5            « a variável do main continua com 5

Na chamada dobrar(pontos), o Java copiou o valor 5 para o parâmetro numero. São duas caixinhas diferentes na memória:

Momentopontos (no main)numero (no dobrar)
Antes da chamada5(não existe)
Começo do dobrar: cópia55
Depois de numero = numero * 2510
O método termina5(apagado)

É como fotocopiar uma prova e entregar a cópia: o aluno pode rabiscar a cópia o quanto quiser, a original continua limpa. Isso vale para todos os tipos primitivos: int, double, char, boolean… Se você quer que o método produza um valor novo para o main usar, ele precisa devolver esse valor com return, o assunto da próxima aula, Valores de Retorno.

Com arrays: a cópia é do endereço

Agora o mesmo teste com um array. A Ana quer dar um ponto de bônus em todas as notas:

import java.util.Arrays;

public class Main {
    static void darBonus(int[] notas) {
        for (int i = 0; i < notas.length; i++) {
            notas[i] = notas[i] + 1;
        }
        System.out.println("Dentro do método: " + Arrays.toString(notas));
    }

    public static void main(String[] args) {
        int[] notas = {7, 8, 6};
        darBonus(notas);
        System.out.println("No main: " + Arrays.toString(notas));
    }
}
$ javac Main.java && java Main
Dentro do método: [8, 9, 7]
No main: [8, 9, 7]      « o array do main MUDOU

Desta vez o main viu a mudança. Isso contradiz a passagem por valor? Não. A explicação está no que uma variável de array guarda: ela não guarda os números, guarda o endereço de onde os números estão na memória. Esse endereço se chama referência.

  • A variável notas do main guarda uma referência: "os números estão no armário 42".
  • Na chamada, o Java copia essa referência para o parâmetro. Agora há duas variáveis com o mesmo endereço: "armário 42".
  • notas[i] = … dentro do método abre o armário 42 e troca o que está lá dentro.
  • Quando o main abre o armário 42, encontra os números novos.

A prova de que a referência foi copiada (e não compartilhada) é este outro teste. Em vez de mexer dentro do array, o método faz o parâmetro apontar para um array novo:

import java.util.Arrays;

public class Main {
    static void trocarLista(int[] notas) {
        notas = new int[] {10, 10, 10};
        System.out.println("Dentro do método: " + Arrays.toString(notas));
    }

    public static void main(String[] args) {
        int[] notas = {7, 8, 6};
        trocarLista(notas);
        System.out.println("No main: " + Arrays.toString(notas));
    }
}
$ javac Main.java && java Main
Dentro do método: [10, 10, 10]
No main: [7, 8, 6]      « o main não foi afetado

O notas = new int[] {…} trocou o endereço guardado na cópia: o parâmetro passou a apontar para o "armário 77", com três 10. A variável do main continuou com "armário 42", intacta.

O que o método faz com o parâmetroPrimitivo (int, double…)Array (e, mais tarde, objetos)
Muda o parâmetro inteiro (x = …)Quem chamou não vêQuem chamou não vê
Muda o conteúdo (x[0] = …)(não existe)Quem chamou vê, porque é o mesmo armário
E a String?

Uma String também é passada por referência copiada, mas ela é imutável: nenhum método muda o texto que está lá dentro. texto.toUpperCase() cria um texto novo. Então, na prática, ela se comporta como um primitivo:

$ java Main.java
Dentro do método: BANANA
No main: banana

(O método fez texto = texto.toUpperCase();, que só troca a cópia, como no trocarLista.) Os objetos que você vai criar em Classes e Objetos seguem a regra dos arrays: mudar um campo dentro do método aparece para quem chamou.

Brinque com o código: rode como está. Depois troque o int pontos por um array int[] pontos = {5}, o parâmetro por int[] numero e a conta por numero[0] = numero[0] * 2;. O que o main mostra agora?

public class Main {
    static void dobrar(int numero) {
        numero = numero * 2;
        System.out.println("Dentro do método: " + numero);
    }

    public static void main(String[] args) {
        int pontos = 5;
        dobrar(pontos);
        System.out.println("No main: " + pontos);
    }
}

Varargs: quantos argumentos vierem

Às vezes você não sabe quantos valores vão chegar. A Ana quer um método que some as notas de um aluno, e cada aluno fez um número diferente de provas. Para isso existe o varargs (de variable arguments, "argumentos variáveis"): três pontos depois do tipo.

public class Main {
    static void mostrarSoma(int... numeros) {
        int total = 0;
        for (int n : numeros) {
            total = total + n;
        }
        System.out.println(numeros.length + " número(s), soma = " + total);
    }

    public static void main(String[] args) {
        mostrarSoma();
        mostrarSoma(10);
        mostrarSoma(7, 8, 6);
        mostrarSoma(1, 2, 3, 4, 5);
        int[] notas = {9, 9, 9};
        mostrarSoma(notas);
    }
}
$ javac Main.java && java Main
0 número(s), soma = 0       « nenhum argumento: um array vazio
1 número(s), soma = 10
3 número(s), soma = 21
5 número(s), soma = 15
3 número(s), soma = 27      « também aceita um array pronto
int...
"Zero ou mais int". Os três pontos ficam colados ao tipo (com ou sem espaço depois).
numeros
Dentro do método, numeros é um array de int comum: tem .length, funciona com for e for-each. O Java monta esse array com os argumentos da chamada.
mostrarSoma(7, 8, 6)
Você passa os valores soltos, separados por vírgula. O Java junta os três num array {7, 8, 6}.
mostrarSoma()
Sem argumentos também vale: numeros chega como um array vazio (length 0), nunca null.

Você já usou um método com varargs: o System.out.printf("%s tem %d anos", nome, idade) aceita quantos valores você quiser depois do texto (veja Formatar Texto).

Varargs com outros parâmetros

Um método pode ter parâmetros normais e um varargs, mas o varargs tem de ser o último (e só pode haver um):

public class Main {
    static void mostrarNotas(String aluno, int... notas) {
        System.out.print(aluno + ":");
        for (int n : notas) {
            System.out.print(" " + n);
        }
        System.out.println();
    }

    public static void main(String[] args) {
        mostrarNotas("Bruno", 7, 8, 6);
        mostrarNotas("Carla", 10);
        mostrarNotas("Davi");
    }
}
$ javac Main.java && java Main
Bruno: 7 8 6
Carla: 10
Davi:

O primeiro argumento vai para aluno; todos os outros, para notas. Se você inverter a ordem na declaração:

$ javac Main.java
Main.java:2: error: varargs parameter must be the last parameter
    static void mostrarNotas(int... notas, String aluno) {
                                    ^
1 error

"O parâmetro varargs precisa ser o último parâmetro". Faz sentido: se ele viesse primeiro, o Java não saberia onde terminam as notas e onde começa o nome.

Varargs ou array?

DeclaraçãoComo chamarQuando usar
void soma(int[] numeros)Só com um array: soma(new int[] {7, 8, 6}) ou soma(notas)Os dados já estão num array (lidos de um arquivo, do teclado…).
void soma(int... numeros)Com valores soltos soma(7, 8, 6), sem nada soma() ou com um array soma(notas)Quem chama vai escrever os valores direto na chamada.

Formas diferentes de mandar dados para um método

Você quer mandar…UseExemplo
Uma informaçãoUm parâmetrosaudar(String nome)
Algumas informações fixasVários parâmetros, separados por vírgulamostrarNota(String aluno, double nota)
Uma quantidade que varia, todos do mesmo tipoVarargs (no fim)mostrarSoma(int... numeros)
Uma lista que já existeUm array (ou, mais tarde, uma ArrayList)darBonus(int[] notas)
Muitas informações sobre a mesma "coisa" (nome, idade, notas de um aluno)Um objeto, que você aprende a criar em Classes e ObjetosmostrarBoletim(Aluno a)
Quantos parâmetros é demais?

Não há limite prático, mas um método com cinco, seis parâmetros fica difícil de chamar: é fácil trocar a ordem de dois double e o compilador não percebe. Quando isso acontecer, é sinal de que os dados querem virar um objeto.


Quando dá errado

"cannot be applied to given types … differ in length"
$ javac Main.java
Main.java:7: error: method saudar in class Main cannot be applied to given types;
        saudar("Ana", "Bruno");
        ^
  required: String
  found:    String,String
  reason: actual and formal argument lists differ in length
1 error

O que aconteceu: o método pede um argumento (required: String) e a chamada mandou dois (found: String,String).

Como resolver: compare a chamada com a declaração. Se o método precisa mesmo receber mais coisas, acrescente parâmetros na declaração; se não, tire argumentos da chamada.

"incompatible types: int cannot be converted to String"
$ javac Main.java
Main.java:7: error: incompatible types: int cannot be converted to String
        saudar(29);
               ^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error

O que aconteceu: o parâmetro é String e o argumento é o número 29. O Java não converte número em texto sozinho numa chamada.

Como resolver: mande um texto (saudar("29") ou saudar(String.valueOf(29))) ou mude o tipo do parâmetro. Se os argumentos estão na ordem trocada, desfaça a troca.

O método mudou a variável, mas o main não viu
$ java Main.java
Dentro do método: 10
No main: 5

O que aconteceu: não é erro do Java: é a passagem por valor. O método recebeu uma cópia do int e mudou só a cópia.

Como resolver: faça o método devolver o valor novo e guarde-o no main: pontos = dobrar(pontos);. Como escrever um método que devolve valor é a próxima aula, Valores de Retorno.

"varargs parameter must be the last parameter"

O que aconteceu: o tipo... não está no fim da lista de parâmetros, ou há dois deles.

Como resolver: mova o varargs para o fim: (String aluno, int... notas). Se precisa de duas listas variáveis, use dois arrays.


Referência

EscritaSignificado
static void m(Tipo nome)Um parâmetro
static void m(Tipo1 a, Tipo2 b)Vários parâmetros; cada um com seu tipo, separados por vírgula
m(valor1, valor2)Chamada: argumentos na mesma quantidade, ordem e tipo dos parâmetros
static void m(int... nums)Varargs: zero ou mais int; dentro do método, nums é um int[]
static void m(String s, int... nums)Parâmetros normais + varargs (sempre o último)
Passagem por valorTodo argumento é copiado. Primitivos: o original nunca muda. Arrays/objetos: a referência é copiada; o conteúdo pode mudar, a variável original não
javac -Xdiags:verboseMostra a mensagem completa dos erros de chamada

Missão final: a maior nota

Missão: escreva o método mostrarMaior, que recebe o nome do aluno e quantas notas vierem (varargs), e imprime a maior, no formato Bruno: maior nota 9. Dica: comece com int maior = notas[0]; e percorra o array (todo aluno tem pelo menos uma nota).

public class Main {
    // escreva o método mostrarMaior aqui

    public static void main(String[] args) {
        mostrarMaior("Bruno", 7, 9, 6, 8);
        mostrarMaior("Carla", 10, 8);
    }
}
public class Main {
    static void mostrarMaior(String aluno, int... notas) {
        int maior = notas[0];
        for (int n : notas) {
            if (n > maior) {
                maior = n;
            }
        }
        System.out.println(aluno + ": maior nota " + maior);
    }

    public static void main(String[] args) {
        mostrarMaior("Bruno", 7, 9, 6, 8);
        mostrarMaior("Carla", 10, 8);
    }
}

Exercícios

1. Declarar o parâmetro

Complete a declaração para o método receber a idade (um número inteiro) com o nome idade:

static void mostrarIdade() {

2. Dois parâmetros

O método é static void mostrarNota(String aluno, double nota). Complete a chamada para mostrar que a Ana tirou 9.5:

mostrarNota();

3. Varargs

Complete o parâmetro para o método aceitar quantos double vierem, com o nome valores:

static void somar() {

4. Em static void saudar(String nome) e na chamada saudar("Bia"), quem é o parâmetro e quem é o argumento?

5. static void zerar(int x) { x = 0; }. No main: int n = 7; zerar(n); System.out.println(n);. O que aparece?

6. static void zerar(int[] v) { v[0] = 0; }. No main: int[] n = {7, 8}; zerar(n); System.out.println(n[0]);. O que aparece?


Resumo

  • Parâmetro: a variável declarada nos parênteses do método (String nome). Argumento: o valor mandado na chamada ("Ana").
  • Vários parâmetros são separados por vírgula e cada um tem o seu tipo. A chamada precisa da mesma quantidade, ordem e tipos.
  • Erros de chamada: cannot be applied to given types (quantidade) e incompatible types (tipo ou ordem). -Xdiags:verbose dá a versão completa.
  • Java sempre passa por valor: o argumento é copiado. Com primitivos, o original nunca muda.
  • Com arrays (e objetos), a referência é copiada: mudar o conteúdo (v[0] = …) aparece para quem chamou; trocar o parâmetro inteiro (v = new …) não.
  • Varargs (int... numeros) aceita zero ou mais valores; dentro do método é um array; tem de ser o último parâmetro.