Java
Tutorial Java: Sobrecarga de métodos
Java sobrecarga: métodos y aplicaciones. Domina la sobrecarga de métodos en Java con ejemplos prácticos y aplicaciones detalladas.
Aprende Java y certifícateConcepto y sintaxis de sobrecarga
La sobrecarga de métodos permite definir múltiples métodos con el mismo nombre pero con diferentes parámetros dentro de una misma clase. Esta técnica aumenta la flexibilidad del código al permitir que un mismo comportamiento se adapte a diferentes tipos o cantidades de datos de entrada.
En Java, se considera que un método está sobrecargado cuando existen varios métodos con el mismo nombre en la misma clase, pero con listas de parámetros diferentes. La sobrecarga se resuelve en tiempo de compilación (compile-time), lo que significa que el compilador determina qué versión del método debe invocarse basándose en los argumentos proporcionados.
Para implementar la sobrecarga de métodos correctamente, se deben seguir ciertas reglas sintácticas:
public class Calculadora {
// Método para sumar dos enteros
public int sumar(int a, int b) {
return a + b;
}
// Método sobrecargado para sumar tres enteros
public int sumar(int a, int b, int c) {
return a + b + c;
}
// Método sobrecargado para sumar dos números decimales
public double sumar(double a, double b) {
return a + b;
}
}
En este ejemplo, el método sumar()
se ha sobrecargado tres veces. Cuando se llama a este método, el compilador selecciona la versión adecuada según los argumentos proporcionados:
Calculadora calc = new Calculadora();
int resultado1 = calc.sumar(5, 3); // Llama a sumar(int, int)
int resultado2 = calc.sumar(5, 3, 2); // Llama a sumar(int, int, int)
double resultado3 = calc.sumar(5.5, 3.5); // Llama a sumar(double, double)
Para que la sobrecarga sea válida, los métodos deben diferenciarse en al menos uno de estos aspectos:
- Número de parámetros: Como en el caso de
sumar(int, int)
ysumar(int, int, int)
. - Tipo de parámetros: Como en
sumar(int, int)
ysumar(double, double)
. - Orden de los parámetros: Aunque menos común, también es válido.
public class Mezclador {
// Diferentes tipos en diferente orden
public String combinar(String texto, int numero) {
return texto + numero;
}
public String combinar(int numero, String texto) {
return numero + texto;
}
}
La sobrecarga de métodos no se basa en:
- El tipo de retorno: No se puede sobrecargar un método cambiando únicamente el tipo de retorno.
- Los nombres de los parámetros: Cambiar solo los nombres de los parámetros no constituye una sobrecarga válida.
- Los modificadores de acceso: Cambiar de
public
aprivate
no crea una sobrecarga.
public class EjemploIncorrecto {
// Esto NO es sobrecarga válida (solo cambia el tipo de retorno)
public int procesar(int valor) { return valor * 2; }
// public double procesar(int valor) { return valor * 2.0; } // Error de compilación
// Esto NO es sobrecarga válida (solo cambian los nombres de parámetros)
public void mostrar(int numero) { System.out.println(numero); }
// public void mostrar(int valor) { System.out.println(valor); } // Error de compilación
}
La sobrecarga de métodos también se aplica a los constructores, lo que permite inicializar objetos de diferentes maneras:
public class Persona {
private String nombre;
private int edad;
// Constructor sin parámetros
public Persona() {
this.nombre = "Desconocido";
this.edad = 0;
}
// Constructor con un parámetro
public Persona(String nombre) {
this.nombre = nombre;
this.edad = 0;
}
// Constructor con dos parámetros
public Persona(String nombre, int edad) {
this.nombre = nombre;
this.edad = edad;
}
}
Un patrón común al implementar constructores sobrecargados es la delegación de constructores mediante la palabra clave this
:
public class Producto {
private String nombre;
private double precio;
private String categoria;
// Constructor completo
public Producto(String nombre, double precio, String categoria) {
this.nombre = nombre;
this.precio = precio;
this.categoria = categoria;
}
// Constructor que delega al constructor completo
public Producto(String nombre, double precio) {
this(nombre, precio, "General");
}
// Otro constructor que delega
public Producto(String nombre) {
this(nombre, 0.0);
}
}
La sobrecarga de métodos también se utiliza a menudo en las clases de utilidad para proporcionar variantes convenientes de operaciones comunes:
public class StringUtils {
// Versión básica
public static boolean isEmpty(String str) {
return str == null || str.length() == 0;
}
// Versión sobrecargada que también considera espacios en blanco
public static boolean isEmpty(String str, boolean trimFirst) {
if (trimFirst && str != null) {
return str.trim().length() == 0;
}
return isEmpty(str);
}
}
En el desarrollo de APIs fluidas, la sobrecarga se utiliza para proporcionar diferentes opciones manteniendo una interfaz coherente:
public class QueryBuilder {
private StringBuilder query = new StringBuilder();
// Diferentes versiones de where() sobrecargadas
public QueryBuilder where(String condition) {
query.append(" WHERE ").append(condition);
return this;
}
public QueryBuilder where(String field, String operator, String value) {
return where(field + " " + operator + " '" + value + "'");
}
public QueryBuilder where(String field, String value) {
return where(field, "=", value);
}
}
La sobrecarga de métodos se utiliza mucho en el API estándar de Java. Por ejemplo, la clase String
tiene múltiples métodos sobrecargados como indexOf()
, substring()
y replace()
. Esta técnica permite que las APIs sean más intuitivas y fáciles de usar, ya que los desarrolladores pueden llamar al mismo método con diferentes tipos de argumentos según sus necesidades.
Sobrecarga vs. sobreescritura
La sobrecarga y la sobreescritura son dos conceptos que, aunque puedan parecer similares, tienen propósitos y comportamientos completamente diferentes. Mientras que la sobrecarga permite definir múltiples métodos con el mismo nombre en una misma clase, la sobreescritura permite redefinir el comportamiento de un método heredado de una clase padre.
La principal diferencia entre ambos conceptos radica en su contexto de aplicación. La sobrecarga ocurre dentro de una misma clase, mientras que la sobreescritura se produce en una relación de herencia entre clases. Esta distinción es crucial para entender cómo se comporta cada mecanismo y cuándo es apropiado utilizar uno u otro.
Veamos las diferencias clave entre sobrecarga y sobreescritura:
Definición:
- Sobrecarga: Múltiples métodos con el mismo nombre pero diferentes parámetros en la misma clase.
- Sobreescritura: Redefinición de un método heredado manteniendo la misma firma.
Resolución:
- Sobrecarga: Se resuelve en tiempo de compilación (enlace estático).
- Sobreescritura: Se resuelve en tiempo de ejecución (enlace dinámico).
Firma del método:
- Sobrecarga: Debe cambiar el número, tipo o el orden de los parámetros.
- Sobreescritura: Debe mantener exactamente la misma firma (nombre, parámetros y tipo de retorno compatible).
Examinemos un ejemplo que ilustra ambos conceptos:
// Clase padre
class Animal {
public void hacerSonido() {
System.out.println("El animal hace un sonido");
}
// Método sobrecargado
public void hacerSonido(int veces) {
for (int i = 0; i < veces; i++) {
System.out.println("El animal hace un sonido");
}
}
}
// Clase hija
class Perro extends Animal {
// Método sobreescrito
@Override
public void hacerSonido() {
System.out.println("El perro ladra: Guau guau");
}
// Método sobrecargado en la clase hija
public void hacerSonido(String intensidad) {
if (intensidad.equals("fuerte")) {
System.out.println("GUAU GUAU!!!");
} else {
System.out.println("guau guau...");
}
}
}
En este ejemplo, la clase Animal
tiene dos versiones del método hacerSonido()
: una sin parámetros y otra con un parámetro entero. Esto es un ejemplo de sobrecarga. Por otro lado, la clase Perro
sobreescribe el método hacerSonido()
sin parámetros para proporcionar una implementación específica para los perros, y además añade otra sobrecarga que acepta un parámetro de tipo String
.
Cuando se utilizan estos métodos, el comportamiento es el siguiente:
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
animal.hacerSonido(); // "El animal hace un sonido"
animal.hacerSonido(3); // Repite 3 veces "El animal hace un sonido"
Perro perro = new Perro();
perro.hacerSonido(); // "El perro ladra: Guau guau"
perro.hacerSonido(2); // Hereda el método sobrecargado y repite 2 veces
perro.hacerSonido("fuerte"); // "GUAU GUAU!!!"
// Polimorfismo en acción
Animal animalPerro = new Perro();
animalPerro.hacerSonido(); // "El perro ladra: Guau guau" (sobreescritura)
animalPerro.hacerSonido(2); // Usa el método sobrecargado de Animal
}
}
En la sobreescritura, se utiliza la anotación @Override
para indicar explícitamente que se está sobreescribiendo un método de la clase padre. Aunque esta anotación es opcional, se recomienda su uso porque:
- Ayuda al compilador a verificar que realmente se está sobreescribiendo un método.
- Mejora la legibilidad del código, indicando claramente la intención.
- Previene errores si la firma del método cambia en la clase padre.
class Gato extends Animal {
// El compilador verificará que este método realmente sobreescriba
// un método en la clase padre
@Override
public void hacerSonido() {
System.out.println("El gato maúlla: Miau miau");
}
}
Existen algunas reglas adicionales para la sobreescritura que no se aplican a la sobrecarga:
- El método sobreescrito no puede tener un modificador de acceso más restrictivo que el método original.
- El método sobreescrito puede tener un tipo de retorno más específico (covariante).
- El método sobreescrito no puede lanzar excepciones comprobadas más amplias o nuevas.
class Padre {
protected Number calcular() throws IOException {
return 0;
}
}
class Hijo extends Padre {
// Válido: acceso menos restrictivo y tipo de retorno más específico
@Override
public Integer calcular() throws FileNotFoundException {
return 42;
}
// Inválido: no se puede sobreescribir con una excepción más amplia
// @Override
// public Integer calcular() throws Exception { ... }
}
La sobreescritura está estrechamente relacionada con el polimorfismo. Cuando se llama a un método en una referencia de tipo padre que apunta a un objeto de tipo hijo, se ejecuta la versión sobreescrita del método:
Animal[] animales = {new Animal(), new Perro(), new Gato()};
for (Animal a : animales) {
a.hacerSonido(); // Llamará a la versión apropiada según el tipo real
}
En contraste, la sobrecarga no exhibe este comportamiento polimórfico en tiempo de ejecución, ya que la selección del método se basa en el tipo de referencia conocido en tiempo de compilación:
Animal animalPerro = new Perro();
// Llama al método de Animal, no al método sobrecargado de Perro
animalPerro.hacerSonido("fuerte"); // Error de compilación
Los constructores pueden ser sobrecargados pero no sobreescritos, ya que son especiales y no se heredan. Sin embargo, se puede utilizar super()
para llamar a un constructor de la clase padre:
class Vehiculo {
protected String marca;
public Vehiculo() {
this.marca = "Desconocida";
}
public Vehiculo(String marca) {
this.marca = marca;
}
}
class Coche extends Vehiculo {
private String modelo;
public Coche() {
super(); // Llama al constructor sin parámetros de Vehiculo
this.modelo = "Desconocido";
}
public Coche(String marca, String modelo) {
super(marca); // Llama al constructor con parámetros de Vehiculo
this.modelo = modelo;
}
}
Reglas de resolución de ambigüedad
Cuando se trabaja con métodos sobrecargados en Java, el compilador debe determinar qué versión del método invocar en cada llamada. Este proceso, conocido como resolución de sobrecarga, sigue un conjunto de reglas específicas para resolver posibles ambigüedades.
El proceso de resolución se realiza en tiempo de compilación y sigue una secuencia de pasos bien definida. Cuando se encuentra una llamada a un método sobrecargado, el compilador busca el método más específico que coincida con los argumentos proporcionados.
public class EjemploAmbiguedad {
public void procesar(int numero) {
System.out.println("Procesando entero: " + numero);
}
public void procesar(double numero) {
System.out.println("Procesando decimal: " + numero);
}
public void procesar(Object objeto) {
System.out.println("Procesando objeto: " + objeto);
}
}
Cuando se llama a estos métodos, el compilador selecciona la versión más específica:
EjemploAmbiguedad ejemplo = new EjemploAmbiguedad();
ejemplo.procesar(10); // Llama a procesar(int)
ejemplo.procesar(10.5); // Llama a procesar(double)
ejemplo.procesar("texto"); // Llama a procesar(Object)
La resolución de sobrecarga sigue estas reglas de prioridad:
- Coincidencia exacta: Se prefiere un método cuyo tipo de parámetro coincida exactamente con el tipo del argumento.
- Promoción de tipos primitivos: Si no hay coincidencia exacta, se consideran las promociones automáticas (por ejemplo,
byte
→short
→int
→long
→float
→double
). - Autoboxing/unboxing: Si no hay coincidencia después de las promociones, se considera la conversión entre tipos primitivos y sus equivalentes en wrapper (por ejemplo,
int
↔Integer
). - Varargs: Los métodos con parámetros varargs tienen la menor prioridad.
Veamos un ejemplo que ilustra estas reglas:
public class ResolucionSobrecarga {
// Método 1
public void mostrar(int x) {
System.out.println("int: " + x);
}
// Método 2
public void mostrar(long x) {
System.out.println("long: " + x);
}
// Método 3
public void mostrar(Integer x) {
System.out.println("Integer: " + x);
}
// Método 4
public void mostrar(Object x) {
System.out.println("Object: " + x);
}
// Método 5
public void mostrar(int... x) {
System.out.println("varargs: " + Arrays.toString(x));
}
}
Al llamar a estos métodos:
ResolucionSobrecarga rs = new ResolucionSobrecarga();
int numero = 10;
rs.mostrar(numero); // Llama al método 1 (coincidencia exacta)
rs.mostrar(10L); // Llama al método 2 (coincidencia exacta con long)
Integer i = 10;
rs.mostrar(i); // Llama al método 3 (coincidencia exacta con Integer)
rs.mostrar(new Object()); // Llama al método 4 (coincidencia exacta con Object)
rs.mostrar(1, 2, 3); // Llama al método 5 (varargs)
Las situaciones de ambigüedad ocurren cuando el compilador no puede determinar qué método es más específico. Esto sucede principalmente en dos escenarios:
- Múltiples métodos igualmente específicos: Cuando dos o más métodos son candidatos igualmente válidos.
- Conversiones incomparables: Cuando se requieren diferentes tipos de conversiones que no tienen una relación de prioridad clara.
public class Ambiguedad {
// Este ejemplo causará un error de compilación
public void metodo(int x, long y) {
System.out.println("Método 1");
}
public void metodo(long x, int y) {
System.out.println("Método 2");
}
public static void main(String[] args) {
Ambiguedad a = new Ambiguedad();
// a.metodo(10, 20); // Error: referencia ambigua a 'metodo'
}
}
En este caso, para resolver la ambigüedad, se debe realizar una conversión explícita:
a.metodo(10, 20L); // Llama a metodo(int, long)
a.metodo(10L, 20); // Llama a metodo(long, int)
Otro caso común de ambigüedad ocurre con el autoboxing y las jerarquías de clases:
public class AmbiguedadAutoboxing {
public void procesar(int x, Integer y) {
System.out.println("Método 1");
}
public void procesar(Integer x, int y) {
System.out.println("Método 2");
}
public static void main(String[] args) {
AmbiguedadAutoboxing aa = new AmbiguedadAutoboxing();
// aa.procesar(10, 20); // Error: referencia ambigua
}
}
Las jerarquías de interfaces también pueden causar ambigüedades, especialmente con tipos genéricos:
public class AmbiguedadInterfaces {
public void procesar(Comparable<String> x) {
System.out.println("Comparable");
}
public void procesar(CharSequence x) {
System.out.println("CharSequence");
}
public static void main(String[] args) {
AmbiguedadInterfaces ai = new AmbiguedadInterfaces();
// String implementa ambas interfaces
// ai.procesar("texto"); // Error: referencia ambigua
}
}
Para resolver este tipo de ambigüedades, se puede utilizar una conversión explícita o crear un método más específico:
ai.procesar((CharSequence) "texto"); // Conversión explícita
// Método más específico que resuelve la ambigüedad
public void procesar(String x) {
System.out.println("String");
}
Un caso especial de ambigüedad ocurre con los métodos varargs. Cuando hay múltiples métodos con varargs de diferentes tipos, se aplican las mismas reglas de especificidad:
public class AmbiguedadVarargs {
public void mostrar(int... nums) {
System.out.println("int varargs");
}
public void mostrar(Integer... nums) {
System.out.println("Integer varargs");
}
public static void main(String[] args) {
AmbiguedadVarargs av = new AmbiguedadVarargs();
// av.mostrar(1, 2, 3); // Error: referencia ambigua
}
}
La resolución de ambigüedad también se complica cuando se trabaja con tipos genéricos debido al borrado de tipos:
public class AmbiguedadGenericos {
// Estos métodos causan un error de compilación por "erasure"
public void procesar(List<String> lista) {
System.out.println("Lista de Strings");
}
public void procesar(List<Integer> lista) {
System.out.println("Lista de Integers");
}
}
Este código no compila porque después del borrado de tipos, ambos métodos tienen la misma firma: procesar(List)
.
Para evitar ambigüedades en el diseño de APIs, se recomienda:
- Mantener la coherencia en los tipos de parámetros para métodos relacionados.
- Evitar sobrecargas que difieran solo en tipos relacionados por herencia o autoboxing.
- Usar nombres diferentes cuando los comportamientos sean significativamente distintos.
- Documentar claramente el comportamiento esperado de cada sobrecarga.
// Mejor diseño para evitar ambigüedades
public class MejorDiseño {
// Nombres diferentes para comportamientos distintos
public void procesarEntero(int valor) { /* ... */ }
public void procesarDecimal(double valor) { /* ... */ }
// Método genérico con parámetro de tipo
public <T> void procesar(List<T> lista, Class<T> tipo) { /* ... */ }
}
En el caso de las clases heredadas, la resolución de sobrecarga se complica aún más porque intervienen tanto los métodos de la clase padre como los de la clase hija:
class Padre {
public void metodo(Number n) {
System.out.println("Padre: Number");
}
}
class Hijo extends Padre {
public void metodo(Integer i) {
System.out.println("Hijo: Integer");
}
public static void main(String[] args) {
Hijo h = new Hijo();
h.metodo(10); // Llama a Hijo.metodo(Integer)
h.metodo(10.5); // Llama a Padre.metodo(Number)
Padre p = new Hijo();
p.metodo(10); // Llama a Padre.metodo(Number) - ¡Importante!
}
}
En el último caso, aunque el objeto es de tipo Hijo
, la referencia es de tipo Padre
, por lo que el compilador busca métodos en la clase Padre
. Esto demuestra que la resolución de sobrecarga se basa en el tipo de referencia (estático), mientras que la resolución de sobreescritura se basa en el tipo del objeto (dinámico).
Cuando se trabaja con constructores sobrecargados, se aplican las mismas reglas de resolución, pero con la particularidad de que se pueden encadenar mediante this()
:
public class Producto {
private String nombre;
private double precio;
public Producto() {
this("Sin nombre", 0.0); // Llama al constructor más específico
}
public Producto(String nombre) {
this(nombre, 0.0);
}
public Producto(String nombre, double precio) {
this.nombre = nombre;
this.precio = precio;
}
}
Este patrón de delegación ayuda a evitar la duplicación de código y mantiene la lógica de inicialización centralizada.
Buenas prácticas y patrones comunes
Mantener la coherencia semántica
Uno de los principios más importantes al sobrecargar métodos es asegurar que todas las versiones realicen la misma operación conceptual. Los métodos sobrecargados deben diferir en cómo procesan diferentes tipos o cantidades de datos, pero no en su propósito fundamental.
// Buena práctica: Coherencia semántica
public class Formateador {
// Todas las versiones realizan la misma operación: formatear a mayúsculas
public String formatear(String texto) {
return texto.toUpperCase();
}
public String formatear(String texto, boolean eliminarEspacios) {
String resultado = texto.toUpperCase();
return eliminarEspacios ? resultado.replace(" ", "") : resultado;
}
}
// Mala práctica: Incoherencia semántica
public class FormateadorIncorrecto {
public String formatear(String texto) {
return texto.toUpperCase(); // Convierte a mayúsculas
}
public String formatear(int numero) {
return String.format("%08d", numero); // Rellena con ceros
// Esta sobrecarga hace algo conceptualmente diferente
}
}
Utilizar nombres descriptivos cuando la semántica difiere
Si necesitas implementar operaciones fundamentalmente diferentes, es mejor utilizar nombres de métodos distintos en lugar de sobrecargar el mismo nombre:
// Mejor enfoque cuando las operaciones son diferentes
public class Conversor {
// Nombres diferentes para operaciones diferentes
public String aTexto(int numero) {
return Integer.toString(numero);
}
public String aFormatoMoneda(double cantidad) {
return String.format("%.2f €", cantidad);
}
public String aFormatoFecha(LocalDate fecha) {
return fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy"));
}
}
Patrón de delegación entre sobrecargas
Un patrón muy útil consiste en implementar la lógica principal en la versión más completa del método y hacer que las versiones más simples deleguen en ella con valores predeterminados:
public class Buscador {
// Versión completa con toda la funcionalidad
public List<Resultado> buscar(String consulta, int limite, boolean caseSensitive) {
// Implementación completa
// ...
return resultados;
}
// Versiones simplificadas que delegan
public List<Resultado> buscar(String consulta, int limite) {
return buscar(consulta, limite, false); // Valor predeterminado para caseSensitive
}
public List<Resultado> buscar(String consulta) {
return buscar(consulta, 10); // Valor predeterminado para limite
}
}
Este enfoque reduce la duplicación de código y centraliza la lógica, facilitando el mantenimiento y reduciendo la posibilidad de errores.
Patrón Builder con sobrecarga de métodos
La sobrecarga se combina eficazmente con el patrón Builder para crear APIs fluidas que permiten configurar objetos de manera flexible:
public class EmailBuilder {
private String destinatario;
private String asunto = "";
private String cuerpo = "";
private List<String> adjuntos = new ArrayList<>();
public EmailBuilder para(String email) {
this.destinatario = email;
return this;
}
// Sobrecargas para diferentes tipos de contenido
public EmailBuilder conAsunto(String asunto) {
this.asunto = asunto;
return this;
}
public EmailBuilder conCuerpo(String texto) {
this.cuerpo = texto;
return this;
}
public EmailBuilder conCuerpo(String texto, boolean html) {
this.cuerpo = html ? "<html><body>" + texto + "</body></html>" : texto;
return this;
}
public EmailBuilder adjuntar(String archivo) {
this.adjuntos.add(archivo);
return this;
}
public EmailBuilder adjuntar(List<String> archivos) {
this.adjuntos.addAll(archivos);
return this;
}
public Email construir() {
return new Email(destinatario, asunto, cuerpo, adjuntos);
}
}
// Uso del builder con métodos sobrecargados
Email email = new EmailBuilder()
.para("destinatario@ejemplo.com")
.conAsunto("Reunión importante")
.conCuerpo("<p>Hola, tenemos una reunión mañana.</p>", true)
.adjuntar("informe.pdf")
.construir();
Evitar sobrecargas que puedan causar confusión
Se deben evitar sobrecargas que puedan llevar a comportamientos inesperados o confusos:
// Potencialmente confuso
public class Procesador {
public void procesar(List<String> elementos) {
// Procesa la lista completa
}
public void procesar(String... elementos) {
// Procesa los elementos individuales
}
}
// Uso problemático
Procesador p = new Procesador();
List<String> lista = List.of("a", "b", "c");
p.procesar(lista); // Llama a la primera versión
String[] array = {"a", "b", "c"};
p.procesar(array); // Llama a la segunda versión
// Pero esto es confuso:
p.procesar(null); // ¿Qué versión se llama? ¿Qué comportamiento espera el usuario?
Patrón de método adaptador
Cuando se trabaja con APIs externas o código heredado, la sobrecarga puede utilizarse para crear métodos adaptadores que simplifiquen el uso de interfaces complejas:
public class ClienteAPI {
private ServicioExterno servicio;
// Método adaptador simplificado
public Resultado consultar(String id) {
return consultar(id, OpcionesConsulta.predeterminadas());
}
// Método adaptador con opciones personalizadas
public Resultado consultar(String id, OpcionesConsulta opciones) {
ValidadorParametros.validar(id, opciones);
ContextoEjecucion contexto = new ContextoEjecucion(opciones);
return servicio.ejecutarConsulta(id, contexto, opciones.getTimeout(),
opciones.isIncluirMetadatos());
}
}
Sobrecarga progresiva para APIs evolutivas
La sobrecarga permite evolucionar APIs añadiendo nuevas funcionalidades sin romper la compatibilidad con código existente:
public class ServicioUsuarios {
// Versión original
public Usuario crear(String nombre, String email) {
return crear(nombre, email, null, false);
}
// Versión añadida en la v2.0
public Usuario crear(String nombre, String email, String telefono) {
return crear(nombre, email, telefono, false);
}
// Versión añadida en la v3.0
public Usuario crear(String nombre, String email, String telefono, boolean verificado) {
// Implementación completa
Usuario usuario = new Usuario();
usuario.setNombre(nombre);
usuario.setEmail(email);
usuario.setTelefono(telefono);
usuario.setVerificado(verificado);
return usuario;
}
}
Evitar la proliferación excesiva de sobrecargas
Aunque la sobrecarga es útil, tener demasiadas variantes puede dificultar la comprensión y el mantenimiento del código:
// Demasiadas sobrecargas pueden ser difíciles de mantener
public class ConexionBD {
public void conectar(String url) { /* ... */ }
public void conectar(String url, String usuario) { /* ... */ }
public void conectar(String url, String usuario, String password) { /* ... */ }
public void conectar(String url, String usuario, String password, int timeout) { /* ... */ }
public void conectar(String url, String usuario, String password, int timeout, boolean ssl) { /* ... */ }
public void conectar(String url, String usuario, String password, int timeout, boolean ssl, String schema) { /* ... */ }
// ...y así sucesivamente
}
// Mejor alternativa: usar un objeto de configuración
public class ConexionBD {
public void conectar(ConfiguracionConexion config) {
// Usa los valores de configuración
}
}
// Con un builder para la configuración
ConfiguracionConexion config = new ConfiguracionConexion.Builder()
.url("jdbc:mysql://localhost:3306/db")
.usuario("admin")
.password("secreto")
.timeout(30)
.ssl(true)
.construir();
Documentación clara de las diferencias entre sobrecargas
Es fundamental documentar claramente las diferencias entre las versiones sobrecargadas, especialmente cuando no son obvias:
public class Analizador {
/**
* Analiza el texto utilizando el algoritmo estándar.
*
* @param texto El texto a analizar
* @return Resultado del análisis
*/
public Resultado analizar(String texto) {
return analizar(texto, false);
}
/**
* Analiza el texto con opción de análisis profundo.
* El análisis profundo examina estructuras semánticas complejas
* pero requiere más tiempo y recursos.
*
* @param texto El texto a analizar
* @param analisisProfundo Si es true, realiza un análisis más exhaustivo
* @return Resultado del análisis
*/
public Resultado analizar(String texto, boolean analisisProfundo) {
// Implementación
return resultado;
}
}
Patrón de método de fábrica con sobrecarga
La sobrecarga se utiliza a menudo en métodos de fábrica estáticos para proporcionar diferentes formas de crear objetos:
public class Periodo {
private LocalDate inicio;
private LocalDate fin;
private Periodo(LocalDate inicio, LocalDate fin) {
this.inicio = inicio;
this.fin = fin;
}
// Métodos de fábrica sobrecargados
public static Periodo entre(LocalDate inicio, LocalDate fin) {
return new Periodo(inicio, fin);
}
public static Periodo deDias(int dias) {
LocalDate hoy = LocalDate.now();
return new Periodo(hoy, hoy.plusDays(dias));
}
public static Periodo deMeses(int meses) {
LocalDate hoy = LocalDate.now();
return new Periodo(hoy, hoy.plusMonths(meses));
}
public static Periodo deAnios(int anios) {
LocalDate hoy = LocalDate.now();
return new Periodo(hoy, hoy.plusYears(anios));
}
}
// Uso
Periodo trimestre = Periodo.deMeses(3);
Periodo semana = Periodo.deDias(7);
Periodo personalizado = Periodo.entre(
LocalDate.of(2023, 1, 1),
LocalDate.of(2023, 12, 31)
);
Considerar alternativas a la sobrecarga excesiva
En algunos casos, existen alternativas más claras a la sobrecarga excesiva:
// En lugar de múltiples sobrecargas para diferentes tipos
public class Convertidor {
public int convertir(String valor) { /* ... */ }
public int convertir(double valor) { /* ... */ }
public int convertir(boolean valor) { /* ... */ }
public int convertir(Date valor) { /* ... */ }
// ...y muchas más
}
// Considerar el uso de genéricos con especialización
public class Convertidor {
public <T> int convertir(T valor) {
if (valor instanceof String) {
return convertirDesdeString((String) valor);
} else if (valor instanceof Double) {
return convertirDesdeDouble((Double) valor);
}
// etc.
throw new IllegalArgumentException("Tipo no soportado: " + valor.getClass());
}
private int convertirDesdeString(String valor) { /* ... */ }
private int convertirDesdeDouble(Double valor) { /* ... */ }
// Métodos privados para cada tipo
}
Evitar sobrecargas que difieran solo en tipos relacionados
Se debe tener especial cuidado con sobrecargas que difieran solo en tipos relacionados por herencia o autoboxing, ya que pueden causar confusión:
// Potencialmente confuso
public class Procesador {
public void procesar(int valor) {
System.out.println("Procesando primitivo: " + valor);
}
public void procesar(Integer valor) {
System.out.println("Procesando objeto: " + valor);
}
}
// El comportamiento puede sorprender a los desarrolladores
Procesador p = new Procesador();
int a = 5;
Integer b = 10;
p.procesar(a); // Llama a procesar(int)
p.procesar(b); // Llama a procesar(Integer)
p.procesar(null); // Llama a procesar(Integer) - ¿Es esto intuitivo?
Pruebas específicas para cada sobrecarga
Es importante probar cada sobrecarga de manera independiente para asegurar que todas funcionan correctamente:
@Test
public void testCalculadoraSumaEnteros() {
Calculadora calc = new Calculadora();
assertEquals(5, calc.sumar(2, 3));
}
@Test
public void testCalculadoraSumaDecimales() {
Calculadora calc = new Calculadora();
assertEquals(5.5, calc.sumar(2.5, 3.0), 0.001);
}
@Test
public void testCalculadoraSumaTresNumeros() {
Calculadora calc = new Calculadora();
assertEquals(10, calc.sumar(2, 3, 5));
}
Otros ejercicios de programación de Java
Evalúa tus conocimientos de esta lección Sobrecarga de métodos con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Streams: match
Gestión de errores y excepciones
CRUD en Java de modelo Customer sobre un ArrayList
Clases abstractas
Listas
Métodos de la clase String
Streams: reduce()
API java.nio 2
Polimorfismo
Pattern Matching
Streams: flatMap()
Llamada y sobrecarga de funciones
Métodos referenciados
Métodos de la clase String
Representación de Fecha
Operadores lógicos
Inferencia de tipos con var
Tipos de datos
Estructuras de iteración
Streams: forEach()
Objetos
Funciones lambda
Uso de Scanner
Tipos de variables
Streams: collect()
Operadores aritméticos
Arrays y matrices
Clases y objetos
Interfaz funcional Consumer
CRUD en Java de modelo Customer sobre un HashMap
Interfaces
Enumeraciones Enums
API Optional
Interfaz funcional Function
Encapsulación
Interfaces
Uso de API Optional
Representación de Hora
Herencia básica
Clases y objetos
Interfaz funcional Supplier
HashMap
Sobrecarga de métodos
Polimorfismo de tiempo de ejecución
OOP en Java
Sobrecarga de métodos
CRUD de productos en Java
Clases sealed
Creación de Streams
Records
Encapsulación
Streams: min max
Herencia
Métodos avanzados de la clase String
Funciones
Polimorfismo de tiempo de compilación
Reto sintaxis Java
Conjuntos
Estructuras de control
Recursión
Excepciones
Herencia avanzada
Estructuras de selección
Uso de interfaces
Operadores
Variables
HashSet
Objeto Scanner
Streams: filter()
Operaciones de Streams
Interfaz funcional Predicate
Streams: sorted()
Configuración de entorno
Uso de variables
Clases
Streams: distinct()
Streams: count()
ArrayList
Mapas
Datos de referencia
Interfaces funcionales
Métodos básicos de la clase String
Tipos de datos
Clases abstractas
Instalación
Funciones
Excepciones
Estructuras de control
Herencia de clases
La clase Scanner
Generics
Streams: map()
Funciones y encapsulamiento
Todas las lecciones de Java
Accede a todas las lecciones de Java y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Instalación De Java
Introducción Y Entorno
Configuración De Entorno Java
Introducción Y Entorno
Tipos De Datos
Sintaxis
Variables
Sintaxis
Operadores
Sintaxis
Estructuras De Control
Sintaxis
Funciones
Sintaxis
Recursión
Sintaxis
Arrays Y Matrices
Sintaxis
Excepciones
Programación Orientada A Objetos
Clases Y Objetos
Programación Orientada A Objetos
Encapsulación
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Clases Abstractas
Programación Orientada A Objetos
Interfaces
Programación Orientada A Objetos
Sobrecarga De Métodos
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
La Clase Scanner
Programación Orientada A Objetos
Métodos De La Clase String
Programación Orientada A Objetos
Excepciones
Programación Orientada A Objetos
Records
Programación Orientada A Objetos
Pattern Matching
Programación Orientada A Objetos
Inferencia De Tipos Con Var
Programación Orientada A Objetos
Enumeraciones Enums
Programación Orientada A Objetos
Generics
Programación Orientada A Objetos
Clases Sealed
Programación Orientada A Objetos
Listas
Framework Collections
Conjuntos
Framework Collections
Mapas
Framework Collections
Funciones Lambda
Programación Funcional
Interfaz Funcional Consumer
Programación Funcional
Interfaz Funcional Predicate
Programación Funcional
Interfaz Funcional Supplier
Programación Funcional
Interfaz Funcional Function
Programación Funcional
Métodos Referenciados
Programación Funcional
Creación De Streams
Programación Funcional
Operaciones Intermedias Con Streams: Map()
Programación Funcional
Operaciones Intermedias Con Streams: Filter()
Programación Funcional
Operaciones Intermedias Con Streams: Distinct()
Programación Funcional
Operaciones Finales Con Streams: Collect()
Programación Funcional
Operaciones Finales Con Streams: Min Max
Programación Funcional
Operaciones Intermedias Con Streams: Flatmap()
Programación Funcional
Operaciones Intermedias Con Streams: Sorted()
Programación Funcional
Operaciones Finales Con Streams: Reduce()
Programación Funcional
Operaciones Finales Con Streams: Foreach()
Programación Funcional
Operaciones Finales Con Streams: Count()
Programación Funcional
Operaciones Finales Con Streams: Match
Programación Funcional
Api Optional
Programación Funcional
Transformación
Programación Funcional
Reducción Y Acumulación
Programación Funcional
Mapeo
Programación Funcional
Streams Paralelos
Programación Funcional
Agrupación Y Partición
Programación Funcional
Filtrado Y Búsqueda
Programación Funcional
Api Java.nio 2
Entrada Y Salida Io
Fundamentos De Io
Entrada Y Salida Io
Leer Y Escribir Archivos
Entrada Y Salida Io
Httpclient Moderno
Entrada Y Salida Io
Clases De Nio2
Entrada Y Salida Io
Api Java.time
Api Java.time
Localtime
Api Java.time
Localdatetime
Api Java.time
Localdate
Api Java.time
Executorservice
Concurrencia
Virtual Threads (Project Loom)
Concurrencia
Future Y Completablefuture
Concurrencia
Spring Framework
Frameworks Para Java
Micronaut
Frameworks Para Java
Maven
Frameworks Para Java
Gradle
Frameworks Para Java
Lombok Para Java
Frameworks Para Java
Quarkus
Frameworks Para Java
Ecosistema Jakarta Ee De Java
Frameworks Para Java
Introducción A Junit 5
Testing
Certificados de superación de Java
Supera todos los ejercicios de programación del curso de Java y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.
En esta lección
Objetivos de aprendizaje de esta lección
- Entender qué es la sobrecarga de métodos en Java
- Comprender las reglas para sobrecargar métodos en Java
- Aprender a escribir métodos sobrecargados en una clase
- Distinguir entre métodos sobrecargados basándose en los parámetros que reciben
- Entender por qué la sobrecarga de métodos es útil y cómo puede mejorar la legibilidad y mantenibilidad del código
- Familiarizarse con los errores de compilación que pueden surgir al intentar sobrecargar métodos incorrectamente