Sobrecarga de Métodos em Java
Defina vários métodos com o mesmo nome mas parâmetros diferentes em Java para criar APIs flexíveis.
Sobrecarga de métodos consiste em declarar dois ou mais métodos na mesma classe com o mesmo nome mas listas de parâmetros diferentes. O compilador escolhe qual deles chamar com base nos tipos dos argumentos passados.
Você já usou métodos sobrecarregados sem perceber. System.out.println(...) tem versões que recebem int, double, String, Object, char[], entre outros. Todos se comportam de forma semelhante — imprimem o valor e uma nova linha — mas a implementação difere de acordo com o tipo.
O que significa "lista de parâmetros diferente"
Duas sobrecargas devem diferir em aridade (número de parâmetros) ou nos tipos desses parâmetros em ordem. Não é suficiente diferir apenas em:
- Nomes dos parâmetros
- Tipo de retorno
- Se os parâmetros são
final
public static int square(int n) { return n * n; }
// VALID overloads — different parameter types
public static double square(double n) { return n * n; }
public static long square(long n) { return n * n; }
// VALID overload — different arity
public static int sum(int a, int b) { return a + b; }
public static int sum(int a, int b, int c) { return a + b + c; }
// INVALID — only the return type differs
// public static long square(int n) { return (long) n * n; } // won't compileComo Java escolhe uma sobrecarga
Quando você escreve square(3), o compilador faz isso na seguinte ordem:
- Correspondência exata. Existe uma sobrecarga cujos parâmetros correspondem exatamente aos tipos dos argumentos?
square(3)→square(int). Pronto. - Alargamento. Se não, os argumentos podem ser alargados (por exemplo,
int → long,int → double)? Escolhe a sobrecarga que exige o menor alargamento. - Autoboxing / unboxing. Caso contrário, tenta embalar ou desembalar (
int ↔ Integer). - Varargs. Como último recurso, recorre a uma sobrecarga varargs (veja o capítulo sobre varargs).
Se duas sobrecargas empatam no mesmo passo, a chamada é ambígua e não compilará.
public static void show(int n) { System.out.println("int: " + n); }
public static void show(long n) { System.out.println("long: " + n); }
public static void show(double n) { System.out.println("double: " + n); }
show(3); // exact match → int
show(3L); // exact match → long
show(3.0); // exact match → double
show((short) 3); // widens short → int (closest), picks show(int)Ambiguidade
Quando o compilador não consegue decidir entre duas sobrecargas igualmente boas, ocorre um erro:
public static void f(int a, long b) { /* ... */ }
public static void f(long a, int b) { /* ... */ }
f(1, 2); // ERROR: reference to f is ambiguousAmbas as sobrecargas exigem o alargamento de um argumento de int para long. Nenhuma é "melhor". A solução é desambiguar no ponto de chamada com uma conversão explícita — f(1, 2L) ou f(1L, 2) — ou adicionar uma terceira sobrecarga f(int, int) que trata o caso exatamente.
Sobrecarga em tipos de objeto
Os tipos de referência sobrecarregam da mesma forma, mas com relações de subtipo em vez de alargamento:
public static void log(Object o) { System.out.println("Object: " + o); }
public static void log(String s) { System.out.println("String: " + s); }
log("hello"); // exact match → String
log(42); // autobox to Integer, then Integer is-a Object → log(Object)
log((Object) "hi"); // forces the Object overloadSe você passa uma String e a única sobrecarga aceita Object, Java aceita — String é um Object. Mas se houver uma sobrecarga mais específica, Java a prefere.
Quando a sobrecarga é útil
O objetivo da sobrecarga é oferecer aos chamadores uma API limpa e natural para o tipo. Dois padrões surgem com mais frequência:
Valores padrão. Uma sobrecarga curta chama a longa com valores padrão sensatos:
public static void greet(String name) {
greet(name, 1); // delegate
}
public static void greet(String name, int times) {
for (int i = 0; i < times; i++) {
System.out.println("Hello, " + name);
}
}Conversores de conveniência. Diferentes tipos de entrada, mesma operação lógica:
public static int lengthOf(String s) { return s == null ? 0 : s.length(); }
public static int lengthOf(int[] xs) { return xs == null ? 0 : xs.length; }
public static int lengthOf(int n) { return Integer.toString(n).length(); }O chamador escreve lengthOf(x) sem se preocupar; o compilador encaminha para o corpo correto.
Quando não sobrecarregar
Se duas sobrecargas fariam coisas substancialmente diferentes, dê-lhes nomes diferentes. O leitor de format(x, y) não deve precisar descobrir qual sobrecarga foi escolhida para entender o que a chamada faz. Sobrecargas devem ser variações da mesma ideia, não ideias diferentes compartilhando um nome.
Sobrecarga vs. sobreposição
Esses dois termos soam parecidos, mas resolvem problemas diferentes, e confundi-los é a fonte mais comum de confusão.
- Sobrecarga é mesmo nome, listas de parâmetros diferentes, dentro de uma única classe. Qual método é executado é decidido pelo compilador, olhando apenas para os tipos estáticos (declarados) dos argumentos. Isso é chamado de vinculação estática (em tempo de compilação).
- Sobreposição é mesmo nome, mesma lista de parâmetros, em uma subclasse — substituindo um método herdado. Qual método é executado é decidido pela JVM em tempo de execução, com base no tipo real do objeto. Isso é vinculação dinâmica (em tempo de execução).
Object x = "hello";
log(x); // calls log(Object), NOT log(String) — chosen from x's DECLARED typeMesmo que x contenha uma String em tempo de execução, o compilador só vê Object x, então escolhe a sobrecarga de Object. A seleção de sobrecarga nunca olha para o tipo em tempo de execução — essa é a função da sobreposição. Veja sobreposição de métodos para o lado em tempo de execução da história.
Um exemplo prático
O que vem a seguir
A sobrecarga permite que um nome aponte para vários métodos. Às vezes você quer um único método que chame a si mesmo — resolvendo um problema ao separar uma parte e pedir a uma versão menor do mesmo problema que trate do restante. Isso é recursão.