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.
- 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
javacdiz quando os argumentos não combinam. - A passagem por valor: por que mudar um
intdentro 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 declararString 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âmetronome, 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âmetro | Argumento | |
|---|---|---|
| Onde aparece | Na declaração do método | Na chamada do método |
| Exemplo | String nome em saudar(String nome) | "Ana" em saudar("Ana") |
| O que é | Uma variável, com tipo e nome | Um valor: literal, variável ou conta ("Ana" + "!") |
| Em inglês | parameter (às vezes "formal parameter") | argument (às vezes "actual argument") |
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:
- Quantidade: o número de argumentos é igual ao de parâmetros.
- Ordem: os argumentos seguem a ordem dos parâmetros. Java não tem "argumentos com nome".
- Tipo: cada argumento combina com o tipo do parâmetro, ou pode ser convertido sem perda. Por isso o
9(umint) foi aceito numdoublee virou9.0: é a conversão automática que você viu em Conversão de Tipos. O contrário (umdoublenum parâmetroint) é erro.
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 mensagem | Traduçã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,double | O que a declaração pede: um String e um double. |
found: String | O que a chamada mandou: só um String. |
reason: actual and formal argument lists differ in length | Motivo: 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:
| Momento | pontos (no main) | numero (no dobrar) |
|---|---|---|
| Antes da chamada | 5 | (não existe) |
Começo do dobrar: cópia | 5 | 5 |
Depois de numero = numero * 2 | 5 | 10 |
| O método termina | 5 | (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
notasdomainguarda 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
mainabre 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âmetro | Primitivo (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 |
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 deintcomum: tem.length, funciona comfore 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:
numeroschega como um array vazio (length0), nuncanull.
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ção | Como chamar | Quando 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… | Use | Exemplo |
|---|---|---|
| Uma informação | Um parâmetro | saudar(String nome) |
| Algumas informações fixas | Vários parâmetros, separados por vírgula | mostrarNota(String aluno, double nota) |
| Uma quantidade que varia, todos do mesmo tipo | Varargs (no fim) | mostrarSoma(int... numeros) |
| Uma lista que já existe | Um 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 Objetos | mostrarBoletim(Aluno a) |
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
$ 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.
$ 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.
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.
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
| Escrita | Significado |
|---|---|
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 valor | Todo 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:verbose | Mostra 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:
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:
3. Varargs
Complete o parâmetro para o método aceitar quantos double vierem, com o nome valores:
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) eincompatible types(tipo ou ordem).-Xdiags:verbosedá 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.