Java Sobrecarga
Em Java, dois métodos da mesma classe podem ter o mesmo nome, desde que recebam parâmetros diferentes. Isso se chama sobrecarga (em inglês, overloading). Você já usa sem perceber: o System.out.println aceita números, textos, letras e true/false porque existem várias versões dele, todas com o mesmo nome.
- Como declarar vários métodos com o mesmo nome e listas de parâmetros diferentes.
- O que é a assinatura de um método e por que ela decide tudo.
- Como o Java escolhe qual versão chamar, inclusive quando o argumento é
inte só existe a versãodouble. - O que não é sobrecarga: mudar só o tipo de retorno ou só o nome do parâmetro.
- Os erros reais
already defined,reference … is ambiguouseno suitable method found.
Antes, revise Parâmetros e Argumentos e Valores de Retorno: a sobrecarga é toda sobre listas de parâmetros.
A ideia antes do código: o verbo "abrir"
Em português, você "abre" uma porta, "abre" um pote, "abre" um arquivo e "abre" uma conta no banco. É o mesmo verbo, mas a ação muda conforme o que vem depois. Ninguém inventa "abrirPorta", "abrirPote" e "abrirConta" na conversa: o objeto da frase já deixa claro qual "abrir" é.
A sobrecarga é isso no Java: um nome só (somar) para a mesma ideia, e o Java descobre qual versão usar pelo que você passa entre os parênteses.
O problema: nomes diferentes para a mesma coisa
A Ana quer somar dois inteiros, três inteiros e dois números com vírgula. Sem sobrecarga, ela teria de inventar um nome para cada caso: somarDois, somarTres, somarDecimais… e lembrar qual é qual. Com sobrecarga, todos se chamam somar:
public class Main {
static int somar(int a, int b) {
return a + b;
}
static int somar(int a, int b, int c) {
return a + b + c;
}
static double somar(double a, double b) {
return a + b;
}
public static void main(String[] args) {
System.out.println(somar(2, 3));
System.out.println(somar(2, 3, 4));
System.out.println(somar(2.5, 3.5));
}
}
$ javac Main.java && java Main 5 « somar(int, int) 9 « somar(int, int, int) 6.0 « somar(double, double): o resultado é double
- somar(int a, int b)
- Primeira versão: dois
int. - somar(int a, int b, int c)
- Segunda versão: a quantidade de parâmetros é diferente (três).
- somar(double a, double b)
- Terceira versão: a quantidade é igual à da primeira, mas os tipos são diferentes.
- somar(2, 3)
- Na chamada, o compilador olha os argumentos (dois
int) e escolhe a versão que combina. A escolha é feita na compilação, não durante a execução.
A assinatura do método
O que o Java usa para diferenciar métodos é a assinatura: o nome mais os tipos dos parâmetros, na ordem. As três versões acima têm assinaturas diferentes:
| Declaração | Assinatura | Faz parte da assinatura? |
|---|---|---|
static int somar(int a, int b) | somar(int, int) | Sim: nome, quantidade, tipos e ordem dos parâmetros. Não: o tipo de retorno, os nomes dos parâmetros, o static. |
static int somar(int a, int b, int c) | somar(int, int, int) | |
static double somar(double a, double b) | somar(double, double) |
Regra de ouro: numa classe, não podem existir dois métodos com a mesma assinatura. Para sobrecarregar, a lista de parâmetros tem de mudar em pelo menos um destes pontos: quantidade, tipo ou ordem dos tipos.
Missão: já existem maior(int, int) e maior(double, double). Crie uma terceira versão, maior(int a, int b, int c), que devolva o maior dos três. Dica: ela pode usar a versão de dois: return maior(maior(a, b), c);.
public class Main {
static int maior(int a, int b) {
return a > b ? a : b;
}
static double maior(double a, double b) {
return a > b ? a : b;
}
public static void main(String[] args) {
System.out.println(maior(7, 15, 3));
System.out.println(maior(8.5, 9.0));
}
}
public class Main {
static int maior(int a, int b) {
return a > b ? a : b;
}
static int maior(int a, int b, int c) {
return maior(maior(a, b), c);
}
static double maior(double a, double b) {
return a > b ? a : b;
}
public static void main(String[] args) {
System.out.println(maior(7, 15, 3));
System.out.println(maior(8.5, 9.0));
}
}
Como o Java escolhe a versão
Quando há várias versões, o compilador procura a que combina melhor com os argumentos. Veja um experimento com uma versão int e uma double:
public class Main {
static void mostrar(int x) {
System.out.println("versão int: " + x);
}
static void mostrar(double x) {
System.out.println("versão double: " + x);
}
public static void main(String[] args) {
mostrar(5);
mostrar(5.0);
mostrar(5L);
mostrar('A');
mostrar(2.5f);
}
}
$ javac Main.java && java Main versão int: 5 « 5 é int: combinação exata versão double: 5.0 « 5.0 é double: combinação exata versão double: 5.0 « 5L é long: não cabe em int sem perda, mas cabe em double versão int: 65 « 'A' é char: o char vira int (o código da letra A é 65) versão double: 2.5 « 2.5f é float: vira double
O raciocínio do compilador, simplificado, é este:
- Existe uma versão com os tipos exatos? Use essa. (
mostrar(5)→int;mostrar(5.0)→double.) - Não? Existe uma versão em que os argumentos cabem por conversão automática, sem perda? Essa conversão se chama ampliação (widening):
byte→short→int→long→float→double, echar→int. Se várias servem, use a mais "próxima": por isso o'A'foi parainte não paradouble. - Ainda não? O Java tenta converter primitivos em objetos (
int→Integer, veja Wrappers) e, por último, versões com varargs. - Nada serviu? Erro:
no suitable method found. Duas serviram igualmente bem? Erro:ambiguous.
Brinque com o código: rode e confira as cinco linhas. Depois acrescente uma versão static void mostrar(char x) e veja qual chamada muda de versão. Por fim, apague a versão int: para onde vão o 5 e o 'A'?
public class Main {
static void mostrar(int x) {
System.out.println("versão int: " + x);
}
static void mostrar(double x) {
System.out.println("versão double: " + x);
}
public static void main(String[] args) {
mostrar(5);
mostrar(5.0);
mostrar(5L);
mostrar('A');
mostrar(2.5f);
}
}
O caminho contrário (estreitamento) nunca é automático: um double não entra num parâmetro int. Agora veja o que acontece quando só existe a versão double:
public class Main {
static void mostrar(double x) {
System.out.println("versão double: " + x);
}
public static void main(String[] args) {
mostrar(5);
mostrar(5.0);
}
}
$ javac Main.java && java Main versão double: 5.0 « o 5 (int) foi ampliado para 5.0 versão double: 5.0
Sem a versão int, o 5 é convertido para double e entra na única que existe. É a mesma conversão que você viu em Conversão de Tipos.
A ordem dos tipos também conta
Mudar só a ordem dos tipos já cria uma assinatura nova:
public class Main {
static void cadastrar(int idade, double altura) {
System.out.println("idade e altura");
}
static void cadastrar(double altura, int idade) {
System.out.println("altura e idade");
}
public static void main(String[] args) {
cadastrar(30, 1.65);
cadastrar(1.65, 30);
}
}
$ javac Main.java && java Main idade e altura altura e idade
Funciona, mas é uma armadilha: basta alguém chamar cadastrar(30, 2) (dois int) para o compilador não saber qual escolher. Você verá o erro em Quando dá errado. Em código real, evite sobrecargas que diferem só na ordem.
Você já usa sobrecarga: o println
O System.out.println tem uma versão para cada tipo: println(int), println(double), println(char), println(boolean), println(String), println(char[]), println(Object) e println() sem nada. Cada uma sabe escrever o seu tipo:
public class Main {
public static void main(String[] args) {
System.out.println(42);
System.out.println(3.14);
System.out.println('A');
System.out.println(true);
System.out.println("texto");
char[] letras = {'o', 'i'};
System.out.println(letras);
}
}
$ javac Main.java && java Main 42 3.14 A true texto oi « println(char[]) escreve as letras do array
Outros exemplos da biblioteca do Java: Math.max e Math.abs (versões para int, long, float, double; veja Math) e texto.indexOf, que aceita um caractere ou um texto (veja Métodos de String).
Formas diferentes: sobrecarga para "valores padrão"
Algumas linguagens deixam escrever saudar(nome = "visitante"), com um valor padrão para o parâmetro. Java não tem isso. O jeito Java é a sobrecarga: as versões curtas chamam a versão completa, preenchendo o que falta.
public class Main {
static void saudar() {
saudar("visitante");
}
static void saudar(String nome) {
saudar(nome, "Bom dia");
}
static void saudar(String nome, String cumprimento) {
System.out.println(cumprimento + ", " + nome + "!");
}
public static void main(String[] args) {
saudar();
saudar("Ana");
saudar("Bruno", "Boa noite");
}
}
$ javac Main.java && java Main
Bom dia, visitante! « saudar() → saudar("visitante") → saudar("visitante", "Bom dia")
Bom dia, Ana!
Boa noite, Bruno!
Só a última versão tem o println. Se a Ana quiser mudar o formato da saudação, muda num lugar só. Você vai ver a mesma técnica com construtores, em Construtores (this(…)).
Qual usar?
| Situação | Use |
|---|---|
A mesma ideia com tipos diferentes (somar para int e double) | Sobrecarga por tipo |
A mesma ideia com mais ou menos informações (saudar(), saudar(nome)) | Sobrecarga por quantidade, com as versões curtas chamando a completa |
| Uma quantidade qualquer de valores do mesmo tipo | Varargs (int... numeros), em vez de várias versões |
| Ações realmente diferentes | Nomes diferentes: abrirConta e fecharConta, não dois conta(…) |
Os nomes parecem, mas são coisas diferentes. Sobrecarga (overloading, esta aula): mesmo nome, parâmetros diferentes, na mesma classe. Sobrescrita (overriding): uma classe filha substitui um método herdado, com a mesma assinatura; é o assunto de Sobrescrita (@Override).
O que não é sobrecarga
Mudar só o tipo de retorno
A Ana quis duas médias: uma inteira e uma com vírgula, ambas com dois int:
public class Main {
static int media(int a, int b) {
return (a + b) / 2;
}
static double media(int a, int b) {
return (a + b) / 2.0;
}
public static void main(String[] args) {
System.out.println(media(7, 8));
}
}
$ javac Main.java
Main.java:6: error: method media(int,int) is already defined in class Main
static double media(int a, int b) {
^
1 error
"O método media(int,int) já está definido na classe Main". Repare que a mensagem mostra a assinatura, media(int,int), sem o tipo de retorno: para o Java, os dois métodos são o mesmo. E faz sentido: na chamada media(7, 8) não há nada que diga qual dos dois você quer, já que o retorno só aparece depois da chamada (e pode até ser ignorado).
Como resolver: use nomes diferentes (mediaInteira e media) ou mantenha só a versão double.
Mudar só o nome do parâmetro
$ javac Main.java
Main.java:6: error: method saudar(String) is already defined in class Main
static void saudar(String apelido) {
^
1 error
saudar(String nome) e saudar(String apelido) têm a mesma assinatura, saudar(String). O nome do parâmetro só existe dentro do método; quem chama não o vê.
Quando dá errado
O que aconteceu: dois métodos com a mesma assinatura. Ou você mudou só o tipo de retorno, só o nome do parâmetro, ou colou o método duas vezes.
Como resolver: mude a lista de parâmetros (quantidade, tipo ou ordem), use outro nome ou apague a cópia.
$ javac Main.java
Main.java:13: error: reference to cadastrar is ambiguous
cadastrar(30, 2);
^
both method cadastrar(int,double) in Main and method cadastrar(double,int) in Main match
1 error
O que aconteceu: "a referência a cadastrar é ambígua: os dois métodos combinam". Com cadastrar(30, 2), o 30 serve para a primeira versão (como int) e o 2 serve virando double; na segunda versão, é o contrário. Nenhuma é melhor que a outra, e o compilador se recusa a sortear.
Como resolver: deixe a chamada exata (cadastrar(30, 2.0)) ou, melhor, elimine as sobrecargas que só trocam a ordem dos tipos.
$ javac Main.java
Main.java:11: error: no suitable method found for mostrar(String)
mostrar("cinco");
^
method Main.mostrar(int) is not applicable
(argument mismatch; String cannot be converted to int)
method Main.mostrar(double) is not applicable
(argument mismatch; String cannot be converted to double)
1 error
O que aconteceu: "nenhum método adequado para mostrar(String)". Quando há sobrecarga, o javac lista cada versão que tentou e o motivo de cada uma não servir: a versão int não aceita texto, a versão double também não.
Como resolver: mande um argumento de um tipo aceito (mostrar(5)) ou crie a versão que falta: static void mostrar(String x). Com uma versão só, o erro seria o mais curto incompatible types (veja Parâmetros).
$ java Main.java versão int: 65
O que aconteceu: não é erro do Java. A Ana chamou mostrar('A') esperando ver a letra, mas não existe mostrar(char); o char foi ampliado para int (o código 65).
Como resolver: crie a versão para o tipo que você quer tratar de forma especial (mostrar(char x)) ou converta o argumento de propósito na chamada.
Referência
Pode coexistir com void f(int a, double b)? | Resposta | Por quê |
|---|---|---|
void f(int a) | Sim | Quantidade diferente |
void f(int a, int b) | Sim | Tipo diferente |
void f(double a, int b) | Sim (mas cuidado: chamadas ambíguas) | Ordem dos tipos diferente |
int f(int a, double b) | Não: already defined | Só o retorno mudou |
void f(int x, double y) | Não: already defined | Só os nomes mudaram |
void f(int... a) | Sim | Varargs é int[], outro tipo |
Missão final: área com sobrecarga
Missão: crie duas versões de area: area(double lado), que devolve a área do quadrado (lado × lado), e area(double largura, double altura), que devolve a do retângulo. O main já chama as duas.
public class Main {
// escreva as duas versões de area aqui
public static void main(String[] args) {
System.out.println("Quadrado: " + area(4));
System.out.println("Retângulo: " + area(3, 5));
}
}
public class Main {
static double area(double lado) {
return lado * lado;
}
static double area(double largura, double altura) {
return largura * altura;
}
public static void main(String[] args) {
System.out.println("Quadrado: " + area(4));
System.out.println("Retângulo: " + area(3, 5));
}
}
Exercícios
1. A assinatura
Escreva a assinatura (nome e tipos, sem espaços) de static String juntar(String a, int vezes):
2. Qual versão?
Existem f(int x) e f(double x). Qual tipo de parâmetro recebe a chamada f(10L)?
3. Complete a sobrecarga
Complete a versão sem parâmetros para que ela chame a versão com nome, usando "visitante":
4. Qual par de métodos não compila na mesma classe?
5. Existem mostrar(int) e mostrar(double). O que mostrar('B') faz?
6. Quando o Java escolhe qual versão sobrecarregada chamar?
Resumo
- Sobrecarga: vários métodos com o mesmo nome e listas de parâmetros diferentes (quantidade, tipo ou ordem dos tipos).
- A assinatura é o nome + os tipos dos parâmetros. Não entram o retorno nem os nomes dos parâmetros.
- O compilador escolhe a versão pelos tipos dos argumentos: primeiro a exata, depois por ampliação (
int→long→double,char→int), depois wrappers e varargs. - Só o retorno diferente não é sobrecarga:
method … is already defined. - Duas versões igualmente boas:
reference … is ambiguous. Nenhuma serve:no suitable method found, com a lista de tentativas. - Java não tem parâmetros com valor padrão; use versões curtas que chamam a completa.