Apuntes DAM
Tema 5 de 14PROG · 1º DAM/DAW

Programación

Métodos

Aprende a crear y utilizar métodos para dividir tus programas en partes reutilizables, usando parámetros y valores de retorno.

90 min lecturaIntermedioRevisado el

Introducción a las Funciones

Conforme aumenta la extensión y la complejidad de un programa, es habitual tener que implementar, en distintas partes, la misma funcionalidad, cosa que implica copiar una y otra vez, donde sea necesario, el mismo fragmento de código. Esto genera dos problemas:

  • Duplicidad del código: Aumenta el tamaño del código y lo hace menos legible.
  • Dificultad en el mantenimiento: Cualquier modificación necesaria dentro del fragmento de código repetido tendría que realizarse en todos y cada uno de los lugares donde se encuentra.

Podríamos pensar en utilizar un bucle, pero si el código se repite en lugares separados del programa, esto no es posible.

Problema del código repetido

Imagina que tienes el mismo código copiado en 10 lugares diferentes de tu programa. Si encuentras un error o necesitas hacer un cambio, tendrías que modificarlo en los 10 lugares. ¡Es ineficiente y propenso a errores!
Funciones en programación — Fernando Herrera


Conceptos Básicos de Funciones

La solución para cuando necesitamos la misma funcionalidad en distintos lugares de nuestro código no es más que etiquetar con un nombre un fragmento de código y sustituir en el programa dicho fragmento, en todos los lugares donde aparezca, por el nombre que le hemos asignado.

Ejemplo: Código Repetido

codigo-repetido.java
1public static void main(String[] args) {
2    // ... código
3    
4    // Fragmento repetido #1
5    System.out.println("Voy a saludar tres veces:");
6    for (int i = 0; i < 3; i++) {
7        System.out.println("Hola.");
8    }
9    
10    // ... más código
11    
12    // Fragmento repetido #2
13    System.out.println("Voy a saludar tres veces:");
14    for (int i = 0; i < 3; i++) {
15        System.out.println("Hola.");
16    }
17    
18    // ... otro código
19    
20    // Fragmento repetido #3
21    System.out.println("Voy a saludar tres veces:");
22    for (int i = 0; i < 3; i++) {
23        System.out.println("Hola.");
24    }
25}

Solución: Usar una Función

Cada fragmento de código repetido puede sustituirse por el nombre que le hemos asignado, en nuestro casosaludarTresVeces():

con-funcion.java
1public static void main(String[] args) {
2    // ... código
3    saludarTresVeces();  // Sustitución por una función
4    
5    // ... más código
6    saludarTresVeces();  // Sustitución por una función
7    
8    // ... otro código
9    saludarTresVeces();  // Sustitución por una función
10    
11    // ... resto del código
12}

La función saludarTresVeces() tendrá que definirse, de forma que se especifique el conjunto de instrucciones que la forma:

definicion-funcion.java
1public static void main(String[] args) {
2    saludarTresVeces();
3    // ... más código
4}
5
6// Definición de la función
7static void saludarTresVeces() {
8    System.out.println("Voy a saludar tres veces:");
9    
10    for (int i = 0; i < 3; i++) {
11        System.out.println("Hola.");
12    }
13}

Ubicación de las funciones

La definición de una función puede hacerse antes o después delmain(). Por convención, suele colocarse después.

Concepto de Función

Esto representa el concepto de función: un conjunto de instrucciones agrupadas bajo un nombre y con un objetivo común, que se ejecuta al ser invocada.

Sintaxis General

sintaxis-funcion.java
1static tipo nombreFuncion() {
2    // Cuerpo de la función
3    // Instrucciones que se ejecutarán
4}

Por ahora, definiremos las funciones static y utilizaremos como tipo la función void, que indica que la función no devuelve nada.

Convención de Nombres (Camel Case)

Habitualmente, los nombres de funciones siguen el estilo Camel Case: los nombres comienzan en minúscula, distinguiendo en los nombres compuestos cada palabra mediante la mayúscula inicial.

ejemplos-nombres.java
1// Ejemplos de nombres de funciones en Camel Case
2suma()
3saludarTresVeces()
4calcularRaizCuadrada()
5mostrarTodosDatosCliente()
6validarContrasena()
7obtenerFechaActual()

Terminología Importante

Llamada a la función

Es el nombre de la función, seguido de () paréntesis. Se convierte en una nueva instrucción que podemos utilizar para invocar la función.

llamada.java
1saludarTresVeces();  // Esto es una llamada a la función

Prototipo de la función

Es la declaración de la función, donde se especifica su nombre, el tipo que devuelve y, entre paréntesis, los parámetros de entrada que utiliza.

prototipo.java
1static void saludarTresVeces()  // Este es el prototipo

Cuerpo de la función

Es el bloque de código que ejecuta la función cada vez que se invoca y que aparece entre llaves después del prototipo.

cuerpo.java
1static void saludarTresVeces() {
2    // Este es el cuerpo de la función
3    System.out.println("Voy a saludar tres veces:");
4    for (int i = 0; i < 3; i++) {
5        System.out.println("Hola.");
6    }
7}

Definición de una función

Está formada por el prototipo más el cuerpo de la función.

Ventajas de Usar Funciones

Con esto evitamos:

  • La duplicidad del código: Ya que el código se escribe una única vez, en la definición de la función.
  • La dificultad en el mantenimiento: Ahora, las modificaciones, en el caso de que sean necesarias, solo se realizan en un lugar: en la definición de la función.

Flujo de Ejecución

El comportamiento de una llamada a una función consiste en:

  1. Las instrucciones del programa principal se ejecutan hasta que encuentra la llamada a la función.
  2. La ejecución salta a la definición de la función.
  3. Se ejecuta el cuerpo de la función.
  4. Cuando la ejecución del cuerpo termina, retornamos al punto del programa desde donde se invocó la función.
  5. El programa continúa su ejecución.

Funciones vs Métodos

Los métodos que hemos utilizado de algunas clases de la API (como nextInt() deScanner) en realidad son funciones. Una función y un método son conceptualmente idénticos, la única diferencia está en el nombre, que varía dependiendo del paradigma de programación usado:
  • Programación estructurada: Se llaman funciones
  • Programación orientada a objetos: Se denominan métodos
ejemplo-completo.java
1public class EjemploFunciones {
2    
3    public static void main(String[] args) {
4        System.out.println("Inicio del programa");
5        
6        saludarTresVeces();  // Primera llamada
7        
8        System.out.println("\nOtra parte del código");
9        
10        saludarTresVeces();  // Segunda llamada
11        
12        System.out.println("\nFin del programa");
13    }
14    
15    // Definición de la función
16    static void saludarTresVeces() {
17        System.out.println("Voy a saludar tres veces:");
18        for (int i = 0; i < 3; i++) {
19            System.out.println("Hola.");
20        }
21    }
22}
23
24// Salida:
25// Inicio del programa
26// Voy a saludar tres veces:
27// Hola.
28// Hola.
29// Hola.
30//
31// Otra parte del código
32// Voy a saludar tres veces:
33// Hola.
34// Hola.
35// Hola.
36//
37// Fin del programa
Curso de Java #14 - Funciones en Java — Roelcode


Ámbito de las Variables

En el cuerpo de una función podemos declarar variables, que se conocen como variables locales. El ámbitode estas, es decir, donde pueden utilizarse, es la propia función donde se declaran, no pudiéndose utilizar fuera de ella.

ambito-variables.java
1public static void main(String[] args) {
2    int x = 10;  // Variable local del main
3    
4    miFuncion();
5    
6    // System.out.println(y);  // ERROR: 'y' no existe aquí
7}
8
9static void miFuncion() {
10    int y = 20;  // Variable local de miFuncion
11    
12    System.out.println(y);  // OK: 'y' existe aquí
13    
14    // System.out.println(x);  // ERROR: 'x' no existe aquí
15}

Ámbito de variables

Cada función tiene su propio espacio de variables locales. Las variables declaradas dentro de una función solo existen dentro de esa función y no pueden ser accedidas desde otras funciones o desde el main.

Variables de Bloque

Nada impide que dentro del cuerpo de una función se utilicen sentencias (if,if-else, bucles, etc.) con sus respectivos bloques de instrucciones, donde a su vez, se pueden volver a declarar nuevas variables que se conocen como variables de bloque, siempre y cuando su nombre no coincida con una variable declarada antes, fuera del bloque, ya que esto producirá un error.

variables-bloque.java
1static void ejemploAmbito() {
2    int x = 10;  // Variable local de la función
3    
4    if (x > 5) {
5        int y = 20;  // Variable de bloque (solo existe dentro del if)
6        System.out.println("x = " + x);  // OK: x está disponible
7        System.out.println("y = " + y);  // OK: y está disponible aquí
8    }
9    
10    System.out.println("x = " + x);  // OK: x sigue disponible
11    // System.out.println("y = " + y);  // ERROR: y no existe fuera del if
12    
13    for (int i = 0; i < 3; i++) {
14        int z = i * 2;  // Variable de bloque del for
15        System.out.println(z);
16    }
17    
18    // System.out.println(i);  // ERROR: i no existe fuera del for
19    // System.out.println(z);  // ERROR: z no existe fuera del for
20}
ejemplo-completo-ambito.java
1public class EjemploAmbito {
2    
3    public static void main(String[] args) {
4        int a = 5;
5        System.out.println("En main, a = " + a);
6        
7        funcion1();
8        funcion2();
9        
10        // Las variables de funcion1 y funcion2 no existen aquí
11    }
12    
13    static void funcion1() {
14        int b = 10;  // Variable local de funcion1
15        System.out.println("En funcion1, b = " + b);
16        
17        if (b > 5) {
18            int c = 15;  // Variable de bloque
19            System.out.println("Dentro del if, c = " + c);
20        }
21        
22        // c no existe aquí
23    }
24    
25    static void funcion2() {
26        int d = 20;  // Variable local de funcion2
27        System.out.println("En funcion2, d = " + d);
28        
29        // b, c, y a no existen aquí
30    }
31}
Tipo de VariableÁmbitoEjemplo
Local (función)Toda la función donde se declaraint x = 10;
Bloque (if, for, etc.)Solo dentro del bloqueif(...) { int y; }


Paso de Información a una Función

En ocasiones, una función necesita conocer información externa para poder llevar a cabo su tarea. Veamos un ejemplo: la funciónsaludarTresVeces() es conveniente cuando queremos saludar exactamente tres veces. Si deseamos saludar un número distinto de veces, estaríamos obligados a implementar las funciones: unSaludo(),dosSaludos(),cuatroSaludos(), etc.

Es mucho más práctico implementar la función variosSaludos() a la que se le pasa el número de veces que deseamos saludar. De esta manera, si ejecutamosvariosSaludos(7), saludará siete veces y si ejecutamosvariosSaludos(2), lo hará en dos ocasiones.

funcion-parametros.java
1static void variosSaludos(int veces) {
2    for (int i = 0; i < veces; i++) {
3        System.out.println("Hola.");
4    }
5}

La variable veces es un parámetro de entrada de la funciónvariosSaludos(). Un parámetro de entrada de una función no es más que una variable local a la que se le asigna valores en cada llamada.

Valores en la Llamada

En la llamada a una función se pueden pasar valores que provienen de literales, expresiones o variables.

valores-llamada.java
1// Llamada con un literal
2variosSaludos(2);
3
4// Llamada con una variable
5int n = 3;
6variosSaludos(n);
7
8// Llamada con una expresión
9variosSaludos(2 * n);  // 2 * 3 = 6 saludos
10
11// Llamada con una operación
12int a = 5;
13int b = 2;
14variosSaludos(a + b);  // 5 + 2 = 7 saludos

Parámetros de Entrada

Una función puede definirse para recibir tantos datos como necesite. Por ejemplo, una función que realiza la suma puede definirse para que se le pasen dos valores que sumar; y a otra que indica si una fecha es correcta se le pasarán tres valores: el año, el mes y el día de la fecha.

funcion-suma.java
1// Para una función que calcula y muestra la suma de dos números
2int a = 3;
3suma(a, 2);  // Muestra la suma de a (que vale 3) más 2

Sintaxis con Múltiples Parámetros

sintaxis-parametros.java
1static tipo nombreFuncion(tipo1 parametro1, tipo2 parametro2, ...) {
2    // Cuerpo de la función
3    // Los parámetros son variables locales inicializadas
4}

El primer parámetro de entrada lo hemos llamado parametro1 y se le puede asignar un valor del tipo tipo1, y lo mismo ocurre con el resto de parámetros. El número de parámetros definidos en la función determina el número de valores que hay que utilizar en cada llamada.

ejemplos-multiples-parametros.java
1// Ejemplo 1: Función suma con dos parámetros
2static void suma(int num1, int num2) {
3    int resultado = num1 + num2;
4    System.out.println("La suma es: " + resultado);
5}
6
7// Llamadas
8suma(5, 3);      // La suma es: 8
9suma(10, 20);    // La suma es: 30
10
11// Ejemplo 2: Función para verificar hora
12static void compruebaHora(int hora, int minutos, int segundos) {
13    if (hora >= 0 && hora < 24 && 
14        minutos >= 0 && minutos < 60 && 
15        segundos >= 0 && segundos < 60) {
16        System.out.println("Hora correcta: " + hora + ":" + minutos + ":" + segundos);
17    } else {
18        System.out.println("Hora incorrecta");
19    }
20}
21
22// Llamadas
23int a = 20;
24int b = 3;
25compruebaHora(a, 4, 2 * b + 1);  // 20:4:7
26
27// Ejemplo 3: Función con diferentes tipos
28static void mostrarDatosPersona(String nombre, int edad, double altura) {
29    System.out.println("Nombre: " + nombre);
30    System.out.println("Edad: " + edad + " años");
31    System.out.println("Altura: " + altura + " metros");
32}
33
34// Llamada
35mostrarDatosPersona("Ana", 25, 1.65);

Paso de Parámetros por Valor

En Java los parámetros toman su valor como una copia del valor de la expresión o variable utilizada en la llamada; este mecanismo de paso de parámetros se denomina paso de parámetros por valor o por copia.

En la figura se aprecia cómo se realiza la copia de los valores de las variables utilizadas en la llamada a las variables empleadas como parámetros.

Importante: Los cambios no afectan al original

Hay que destacar que cualquier cambio en un parámetro de entrada que se efectúe dentro del cuerpo de la función no repercute en la variable o expresión utilizada en la llamada, ya que lo que se modifica es una copia y no el dato original.
paso-por-valor.java
1public static void main(String[] args) {
2    int a = 1, b = 2, c = 3;
3    
4    System.out.println("Antes de llamar: a=" + a + ", b=" + b + ", c=" + c);
5    
6    compruebaHora(a, b, c);  // Llamada
7    
8    System.out.println("Después de llamar: a=" + a + ", b=" + b + ", c=" + c);
9    // a, b y c mantienen sus valores originales
10}
11
12// Definición de la función
13static void compruebaHora(int hora, int minutos, int segundos) {
14    System.out.println("Dentro de la función: hora=" + hora);
15    
16    // Modificamos el parámetro
17    hora = 23;  // Solo modifica la COPIA, no el original
18    
19    System.out.println("Después de modificar: hora=" + hora);
20    
21    // Verificación de la hora
22    if (hora >= 0 && hora < 24 && 
23        minutos >= 0 && minutos < 60 && 
24        segundos >= 0 && segundos < 60) {
25        System.out.println("Hora válida");
26    }
27}
28
29// Salida:
30// Antes de llamar: a=1, b=2, c=3
31// Dentro de la función: hora=1
32// Después de modificar: hora=23
33// Hora válida
34// Después de llamar: a=1, b=2, c=3
35// ¡La variable 'a' sigue valiendo 1!
ejemplo-visual-paso-valor.java
1public static void main(String[] args) {
2    int x = 10;
3    System.out.println("Antes: x = " + x);
4    
5    modificarValor(x);
6    
7    System.out.println("Después: x = " + x);  // x sigue siendo 10
8}
9
10static void modificarValor(int numero) {
11    System.out.println("Dentro función (antes): numero = " + numero);
12    
13    numero = 99;  // Solo modifica la copia
14    
15    System.out.println("Dentro función (después): numero = " + numero);
16}
17
18// Salida:
19// Antes: x = 10
20// Dentro función (antes): numero = 10
21// Dentro función (después): numero = 99
22// Después: x = 10  ← El original NO cambió
FUNCIONES con PARÁMETROS en Java | Curso Java # 17 — UskoKruM2010


Valor Devuelto por una Función

Hemos visto que es posible pasar información hacia la función a través de los parámetros de entrada. También es posible que el paso de información sea en sentido contrario, es decir, desde el cuerpo de la función hacia el código donde se hace la llamada.

Con esto conseguimos que la llamada a una función se convierta en un valor cualquiera. Este puede ser utilizado desde el lugar donde se invoca.

ejemplo-return.java
1// Supongamos que tenemos una función suma() que devuelve un valor
2int a = suma(2, 3);        // a = 5
3int b = suma(7, 1) * 5;    // b = 8 * 5 = 40
4
5// En la primera instrucción: suma(2, 3) se sustituye por 5
6// En la segunda instrucción: suma(7, 1) se sustituye por 8

La Instrucción return

Hasta ahora hemos utilizado siempre void como tipo devuelto por una función, lo que indica que la función no devuelve nada, o dicho de otra forma: la llamada a la función no se sustituye por ningún valor.

Es posible utilizar cualquier tipo para especificar que la llamada a la función se sustituirá por un valor del tipo indicado. ¿Cómo damos ese valor a la llamada de la función? Para ello disponemos de la instrucción return que finaliza la ejecución de la función y devuelve el valor indicado.

Sintaxis General

sintaxis-return.java
1static tipo nombreFuncion(parámetros) {
2    // ... código
3    return valor;  // El tipo de 'valor' debe coincidir con 'tipo'
4}

Cuándo usar return

La instrucción return se utiliza en funciones con un tipo devuelto distinto a void. Debe existir una concordancia entre el tipo devuelto declarado en la función y el tipo del valor devuelto con return.

Ejemplo: Función Suma

funcion-suma-return.java
1// Función que devuelve un int
2static int suma(int x, int y) {
3    int resultado;
4    resultado = x + y;
5    return resultado;  // Devuelve el valor de resultado
6}
7
8// Uso de la función
9public static void main(String[] args) {
10    int a = suma(5, 3);
11    System.out.println("La suma es: " + a);  // 8
12    
13    // También se puede usar directamente
14    System.out.println("Suma: " + suma(10, 20));  // 30
15    
16    // O en expresiones
17    int b = suma(2, 3) * 2;
18    System.out.println("Resultado: " + b);  // 10
19}

Ejemplos con Diferentes Tipos de Retorno

ejemplos-return-tipos.java
1// Función que devuelve un double
2static double calcularPromedio(double num1, double num2, double num3) {
3    double suma = num1 + num2 + num3;
4    double promedio = suma / 3.0;
5    return promedio;
6}
7
8// Función que devuelve un boolean
9static boolean esPar(int numero) {
10    if (numero % 2 == 0) {
11        return true;
12    } else {
13        return false;
14    }
15    // Forma más concisa: return numero % 2 == 0;
16}
17
18// Función que devuelve un String
19static String obtenerDiaSemana(int dia) {
20    if (dia == 1) return "Lunes";
21    if (dia == 2) return "Martes";
22    if (dia == 3) return "Miércoles";
23    if (dia == 4) return "Jueves";
24    if (dia == 5) return "Viernes";
25    if (dia == 6) return "Sábado";
26    if (dia == 7) return "Domingo";
27    return "Día inválido";
28}
29
30// Función que devuelve el mayor de dos números
31static int maximo(int a, int b) {
32    if (a > b) {
33        return a;
34    } else {
35        return b;
36    }
37}
38
39// Función que calcula el factorial
40static long factorial(int n) {
41    long resultado = 1;
42    for (int i = 1; i <= n; i++) {
43        resultado *= i;
44    }
45    return resultado;
46}
47
48// USO DE LAS FUNCIONES
49public static void main(String[] args) {
50    // Usar calcularPromedio
51    double prom = calcularPromedio(8.5, 7.0, 9.5);
52    System.out.println("Promedio: " + prom);  // 8.333...
53    
54    // Usar esPar
55    if (esPar(10)) {
56        System.out.println("10 es par");
57    }
58    
59    // Usar obtenerDiaSemana
60    String dia = obtenerDiaSemana(3);
61    System.out.println("Día: " + dia);  // Miércoles
62    
63    // Usar maximo
64    int mayor = maximo(15, 23);
65    System.out.println("Mayor: " + mayor);  // 23
66    
67    // Usar factorial
68    long fact = factorial(5);
69    System.out.println("5! = " + fact);  // 120
70}

Buenas prácticas con return

  • La última instrucción de una función con retorno debe ser return
  • Las instrucciones después de return nunca se ejecutarán
  • Es desaconsejable usar varios return en una misma función (aunque es válido)
  • Una función debe tener un único punto de entrada y salida
codigo-inalcanzable.java
1static int ejemplo() {
2    int x = 10;
3    return x;
4    
5    // ERROR: Todo lo que está después de return es inalcanzable
6    // System.out.println("Esto nunca se ejecuta");
7    // x = 20;
8}
9
10// EVITAR: Múltiples returns (funciona pero no es ideal)
11static int calcular(int x) {
12    if (x < 0) {
13        return -1;  // Return 1
14    }
15    if (x == 0) {
16        return 0;   // Return 2
17    }
18    return x * 2;   // Return 3
19}
20
21// MEJOR: Un solo return
22static int calcularMejor(int x) {
23    int resultado;
24    
25    if (x < 0) {
26        resultado = -1;
27    } else if (x == 0) {
28        resultado = 0;
29    } else {
30        resultado = x * 2;
31    }
32    
33    return resultado;  // Un solo punto de salida
34}
Tutoriales Java: 10 Funciones que devuelven valores (Return) — IkexIke


Sobrecarga de Funciones

Java permite que dos o más funciones compartan el mismo identificador (nombre) en un mismo programa. Esto es lo que se conoce como sobrecarga de funciones.

La forma de distinguir entre las distintas funciones sobrecargadas es mediante su lista de parámetros, que deben ser distintas, ya sea en número o en tipo.

Reglas de la sobrecarga

  • Las funciones sobrecargadas deben tener diferentes parámetros (número o tipo)
  • Las funciones sobrecargadas pueden devolver tipos distintos
  • El tipo de retorno NO sirve para distinguir una función sobrecargada de otra

Ejemplo: Funciones Suma Sobrecargadas

sobrecarga-suma.java
1// Función sobrecargada #1: Suma simple de dos enteros
2static int suma(int a, int b) {
3    int suma;
4    suma = a + b;
5    return suma;
6}
7
8// Función sobrecargada #2: Suma ponderada de dos enteros
9static double suma(int a, double pesoA, int b, double pesoB) {
10    double suma;
11    suma = a * pesoA / (pesoA + pesoB) + b * pesoB / (pesoA + pesoB);
12    return suma;
13}
14
15// USO
16public static void main(String[] args) {
17    // Se ejecuta la primera versión (2 parámetros int)
18    int resultado1 = suma(2, 3);
19    System.out.println("Suma simple: " + resultado1);  // 5
20    
21    // Se ejecuta la segunda versión (4 parámetros: int, double, int, double)
22    double resultado2 = suma(2, 0.25, 3, 0.75);
23    System.out.println("Suma ponderada: " + resultado2);  // 2.75
24}

Más Ejemplos de Sobrecarga

ejemplos-sobrecarga.java
1// Ejemplo 1: Función imprimir sobrecargada
2static void imprimir(int numero) {
3    System.out.println("Número entero: " + numero);
4}
5
6static void imprimir(double numero) {
7    System.out.println("Número decimal: " + numero);
8}
9
10static void imprimir(String texto) {
11    System.out.println("Texto: " + texto);
12}
13
14static void imprimir(int num1, int num2) {
15    System.out.println("Dos números: " + num1 + " y " + num2);
16}
17
18// Ejemplo 2: Función calcularArea sobrecargada
19// Área de un círculo
20static double calcularArea(double radio) {
21    return Math.PI * radio * radio;
22}
23
24// Área de un rectángulo
25static double calcularArea(double base, double altura) {
26    return base * altura;
27}
28
29// Área de un triángulo (con tres parámetros)
30static double calcularArea(double base, double altura, boolean esTriangulo) {
31    if (esTriangulo) {
32        return (base * altura) / 2.0;
33    }
34    return base * altura;
35}
36
37// Ejemplo 3: Función mayor sobrecargada
38static int mayor(int a, int b) {
39    return a > b ? a : b;
40}
41
42static int mayor(int a, int b, int c) {
43    int max = a;
44    if (b > max) max = b;
45    if (c > max) max = c;
46    return max;
47}
48
49static double mayor(double a, double b) {
50    return a > b ? a : b;
51}
52
53// USO DE LAS FUNCIONES SOBRECARGADAS
54public static void main(String[] args) {
55    // Diferentes versiones de imprimir
56    imprimir(10);              // "Número entero: 10"
57    imprimir(3.14);            // "Número decimal: 3.14"
58    imprimir("Hola");          // "Texto: Hola"
59    imprimir(5, 8);            // "Dos números: 5 y 8"
60    
61    // Diferentes versiones de calcularArea
62    double areaCirculo = calcularArea(5.0);           // π * 5²
63    double areaRectangulo = calcularArea(4.0, 6.0);   // 4 * 6
64    double areaTriangulo = calcularArea(4.0, 6.0, true);  // (4*6)/2
65    
66    System.out.println("Área círculo: " + areaCirculo);
67    System.out.println("Área rectángulo: " + areaRectangulo);
68    System.out.println("Área triángulo: " + areaTriangulo);
69    
70    // Diferentes versiones de mayor
71    System.out.println("Mayor de 5 y 8: " + mayor(5, 8));
72    System.out.println("Mayor de 3, 7 y 2: " + mayor(3, 7, 2));
73    System.out.println("Mayor de 3.5 y 2.8: " + mayor(3.5, 2.8));
74}

Sobrecarga en la API de Java

Es muy común encontrar en la API funciones (métodos) sobrecargadas, ya que permiten agrupar distintas funcionalidades, cuyo uso es similar, bajo el mismo identificador. Por ejemplo, la función que más hemos utilizado hasta ahora, System.out.println, se encuentra sobrecargada para poder mostrar en pantalla cualquier tipo de dato.
println-sobrecargado.java
1// System.out.println está sobrecargado para aceptar diferentes tipos
2System.out.println(10);           // println(int)
3System.out.println(3.14);         // println(double)
4System.out.println("Hola");       // println(String)
5System.out.println(true);         // println(boolean)
6System.out.println('A');          // println(char)
Válido para SobrecargaNo Válido
Diferente número de parámetrosSolo diferente tipo de retorno
Diferente tipo de parámetrosSolo diferente nombre de parámetros
Diferente orden de tiposParámetros idénticos
SOBRESCRITURA y SOBRECARGA de MÉTODOS en JAVA | | Curso de POO | Explicación FÁCIL — TodoCode


Recursividad

Una función puede ser invocada desde cualquier lugar: desde el programa principal, desde otra función e incluso desde dentro de su propio cuerpo de instrucciones. En este último caso, cuando una función se invoca a sí misma, diremos que es una función recursiva.

recursiva-basica.java
1static int funcionRecursiva() {
2    funcionRecursiva();  // Llamada recursiva a sí misma
3    // ¡PROBLEMA! Esto crea un bucle infinito
4}

Este es el esquema general de una función recursiva. Si observamos con atención, se plantea un problema: dentro de funcionRecursiva() se invoca afuncionRecursiva(), donde a su vez, se volverá a llamar afuncionRecursiva(), y así sucesivamente. Esto nos lleva a un ciclo infinito de llamadas a la función.

Caso Base

Para evitar el ciclo infinito, hemos de habilitar un mecanismo que detenga, en algún momento, la serie de llamadas recursivas: una sentencia if que, utilizando una condición, llamada "caso base", impida que se continúe con una nueva llamada recursiva.

Esquema General

esquema-recursivo.java
1static int funcionRecursiva(datos) {
2    int resultado;
3    
4    if (caso_base) {
5        // Condición de parada
6        resultado = valorBase;
7    } else {
8        // Llamada recursiva con datos reducidos
9        resultado = funcionRecursiva(nuevosDatos);
10    }
11    
12    return resultado;
13}

Principios de la recursividad

  • Caso base: Condición que detiene las llamadas recursivas
  • Caso recursivo: La función se llama a sí misma con un problema más pequeño
  • Convergencia: Los datos deben tender hacia el caso base

Solo cuando la condición del caso base sea false, se hará una nueva llamada recursiva. Cuando el caso base sea true se romperá la cadena de llamadas.

La idea principal de la recursividad es solucionar un problema reduciendo su tamaño. Este proceso continúa hasta que tenga un tamaño tan pequeño que su solución sea trivial.

Ejemplo: Factorial

Vamos a ver un ejemplo, supongamos que tenemos que calcular el factorial de un número "n" que se representa por "n!".

definicion-factorial
1Definición matemática del factorial:
2n! = n × (n-1) × (n-2) × ... × 2 × 1
3
4Ejemplos:
55! = 5 × 4 × 3 × 2 × 1 = 120
64! = 4 × 3 × 2 × 1 = 24
73! = 3 × 2 × 1 = 6
82! = 2 × 1 = 2
91! = 1
100! = 1 (por definición)

La definición de factorial se puede escribir también del siguiente modo de forma recursiva:

factorial-recursivo
1Definición recursiva:
2n! = n × (n-1)!
3
4Ejemplos:
55! = 5 × 4!
64! = 4 × 3!
73! = 3 × 2!
82! = 2 × 1!
91! = 1 × 0!
100! = 1  ← CASO BASE

Se considera por definición que el factorial de cero vale uno. Para calcular el factorial de un número, estamos utilizando el factorial de un número más pequeño, con lo cual estamos reduciendo el problema.

Hemos de buscar un caso base, es decir, un valor para el que calcular el factorial sea algo trivial y no necesitemos volver a utilizar el método recursivo. El caso base del factorial es: 0! = 1.

Implementación

factorial-recursivo.java
1static long factorial(int n) {
2    long resultado;
3    
4    if (n == 0) {  // Caso base
5        resultado = 1;
6    } else {  // Caso recursivo
7        resultado = n * factorial(n - 1);  // Llamada recursiva
8    }
9    
10    return resultado;
11}
12
13// Forma más compacta
14static long factorialCompacto(int n) {
15    if (n == 0) {
16        return 1;  // Caso base
17    }
18    return n * factorialCompacto(n - 1);  // Caso recursivo
19}

Traza de Ejecución

traza-factorial
1Llamada: factorial(5)
2
3Paso 1: factorial(5)
4  ├─ n = 5, no es caso base
5  └─ return 5 * factorial(4)
6       │
7Paso 2: factorial(4)
8       ├─ n = 4, no es caso base
9       └─ return 4 * factorial(3)
10            │
11Paso 3: factorial(3)
12            ├─ n = 3, no es caso base
13            └─ return 3 * factorial(2)
14                 │
15Paso 4: factorial(2)
16                 ├─ n = 2, no es caso base
17                 └─ return 2 * factorial(1)
18                      │
19Paso 5: factorial(1)
20                      ├─ n = 1, no es caso base
21                      └─ return 1 * factorial(0)
22                           │
23Paso 6: factorial(0)
24                           ├─ n = 0, ¡CASO BASE!
25                           └─ return 1
26
27Ahora se resuelven las llamadas en orden inverso:
28
29factorial(0) = 1
30factorial(1) = 1 * 1 = 1
31factorial(2) = 2 * 1 = 2
32factorial(3) = 3 * 2 = 6
33factorial(4) = 4 * 6 = 24
34factorial(5) = 5 * 24 = 120
35
36Resultado final: 120

Más Ejemplos de Recursividad

ejemplos-recursividad.java
1// Ejemplo 1: Suma de números del 1 al n
2static int sumaHastaN(int n) {
3    if (n == 1) {  // Caso base
4        return 1;
5    }
6    return n + sumaHastaN(n - 1);  // Caso recursivo
7}
8// sumaHastaN(5) = 5 + 4 + 3 + 2 + 1 = 15
9
10// Ejemplo 2: Potencia (a^n)
11static int potencia(int base, int exponente) {
12    if (exponente == 0) {  // Caso base: a^0 = 1
13        return 1;
14    }
15    return base * potencia(base, exponente - 1);
16}
17// potencia(2, 3) = 2 * 2 * 2 = 8
18
19// Ejemplo 3: Fibonacci
20static int fibonacci(int n) {
21    if (n == 0) {  // Caso base 1
22        return 0;
23    }
24    if (n == 1) {  // Caso base 2
25        return 1;
26    }
27    return fibonacci(n - 1) + fibonacci(n - 2);  // Caso recursivo
28}
29// fibonacci(5) = 0, 1, 1, 2, 3, 5
30
31// Ejemplo 4: Contar dígitos de un número
32static int contarDigitos(int numero) {
33    if (numero < 10) {  // Caso base: un solo dígito
34        return 1;
35    }
36    return 1 + contarDigitos(numero / 10);
37}
38// contarDigitos(12345) = 5
39
40// Ejemplo 5: Invertir un número
41static int invertir(int numero, int invertido) {
42    if (numero == 0) {  // Caso base
43        return invertido;
44    }
45    int ultimoDigito = numero % 10;
46    return invertir(numero / 10, invertido * 10 + ultimoDigito);
47}
48// invertir(12345, 0) = 54321
49
50// Ejemplo 6: Máximo común divisor (MCD) - Algoritmo de Euclides
51static int mcd(int a, int b) {
52    if (b == 0) {  // Caso base
53        return a;
54    }
55    return mcd(b, a % b);
56}
57// mcd(48, 18) = 6
58
59// USO DE LAS FUNCIONES
60public static void main(String[] args) {
61    System.out.println("Suma 1 a 5: " + sumaHastaN(5));  // 15
62    System.out.println("2^3: " + potencia(2, 3));  // 8
63    System.out.println("Fibonacci(6): " + fibonacci(6));  // 8
64    System.out.println("Dígitos de 12345: " + contarDigitos(12345));  // 5
65    System.out.println("Invertir 12345: " + invertir(12345, 0));  // 54321
66    System.out.println("MCD(48, 18): " + mcd(48, 18));  // 6
67}

Consideraciones sobre recursividad

  • Ventajas: Código elegante y fácil de entender para problemas que se definen recursivamente
  • Desventajas: Consume más memoria (cada llamada se guarda en la pila) y puede ser más lento que soluciones iterativas
  • Riesgo: Si el caso base está mal definido o los datos no convergen, puede causar desbordamiento de pila (Stack Overflow)
AspectoRecursividadIteración
LegibilidadMás clara para problemas recursivosPuede ser más verbosa
MemoriaUsa más memoria (pila)Más eficiente
VelocidadGeneralmente más lentaMás rápida
RiesgoStack overflow si mal diseñadaBucle infinito (más fácil detectar)
La MAGIA de la RECURSIVIDAD — BettaTech


Ejercicios Prácticos

Ejemplo Guiado
Fácil

Calculadora con Funciones

Crea un programa que implemente una calculadora usando funciones: 1. Crea funciones separadas para: suma, resta, multiplicación, división y módulo 2. Cada función debe recibir dos números y devolver el resultado 3. La función división debe validar que el divisor no sea cero 4. Crea un menú en el main que permita elegir la operación 5. Muestra el resultado usando el valor devuelto por las funciones Requisitos: - Todas las funciones deben devolver un valor (no void) - Usar parámetros para recibir los números - Validar entradas cuando sea necesario Ejemplo de ejecución: === CALCULADORA === 1. Suma 2. Resta 3. Multiplicación 4. División 5. Módulo Elige: 1 Número 1: 10 Número 2: 5 Resultado: 15

Ejercicio Práctico
Medio

Validación de Contraseñas

Crea funciones para validar contraseñas según estos criterios: 1. `longitudValida(password)`: Verifica que tenga entre 8 y 20 caracteres 2. `tieneMayuscula(password)`: Verifica que tenga al menos una mayúscula 3. `tieneMinuscula(password)`: Verifica que tenga al menos una minúscula 4. `tieneDigito(password)`: Verifica que tenga al menos un número 5. `esPasswordSegura(password)`: Usa las funciones anteriores y devuelve true si cumple todos los criterios En el main: - Solicita una contraseña al usuario - Valida usando las funciones - Muestra qué criterios cumple y cuáles no - Indica si la contraseña es segura o no Pista: Usa `Character.isUpperCase()`, `Character.isLowerCase()`, `Character.isDigit()`

Ejercicio Práctico
Difícil

Números Perfectos

Un número perfecto es aquel que es igual a la suma de sus divisores propios (excluyendo el propio número). Ejemplo: 6 es perfecto porque 6 = 1 + 2 + 3 Crea estas funciones: 1. `sumaDivisores(n)`: Devuelve la suma de los divisores propios de n 2. `esPerfecto(n)`: Devuelve true si n es perfecto 3. `mostrarDivisores(n)`: Muestra por pantalla los divisores de n 4. `encontrarPerfectos(limite)`: Encuentra todos los números perfectos hasta el límite En el main: - Solicita un límite al usuario - Encuentra y muestra todos los números perfectos hasta ese límite - Para cada número perfecto, muestra sus divisores Nota: Los primeros números perfectos son: 6, 28, 496, 8128

Ejercicio Práctico
Difícil

Torre de Hanoi (Recursivo)

La Torre de Hanoi es un problema clásico de recursividad. Tienes 3 torres y n discos de diferentes tamaños. Los discos empiezan en la torre A y debes moverlos a la torre C, siguiendo estas reglas: 1. Solo puedes mover un disco a la vez 2. Un disco más grande nunca puede estar sobre uno más pequeño 3. Puedes usar la torre B como auxiliar Crea una función recursiva `hanoi(n, origen, destino, auxiliar)` que: - Recibe el número de discos y los nombres de las torres - Muestra los movimientos necesarios para resolver el puzzle Pista: El caso base es cuando n = 1 (mover directamente). Para n > 1: 1. Mover n-1 discos de origen a auxiliar (usando destino como auxiliar) 2. Mover el disco más grande de origen a destino 3. Mover n-1 discos de auxiliar a destino (usando origen como auxiliar) Ejemplo con 3 discos: Mover disco de A a C Mover disco de A a B Mover disco de C a B Mover disco de A a C Mover disco de B a A Mover disco de B a C Mover disco de A a C

Ejemplo Guiado
Medio

Sobrecarga: Calculadora de Áreas

Crea un programa con funciones sobrecargadas para calcular áreas: 1. `calcularArea(radio)` - Área de un círculo: π × r² 2. `calcularArea(lado)` - Área de un cuadrado: lado² - Debe recibir un parámetro adicional para distinguir (por ejemplo, un String "cuadrado") 3. `calcularArea(base, altura)` - Área de un rectángulo: base × altura 4. `calcularArea(base, altura, tipo)` - Área de un triángulo: (base × altura) / 2 - tipo puede ser "triangulo" En el main: - Muestra un menú con las opciones - Solicita los datos necesarios - Llama a la función apropiada - Muestra el resultado Requisitos: - Todas las funciones deben devolver un double - Usa sobrecarga (mismo nombre, diferentes parámetros) - El menú debe ser claro y fácil de usar

Practica con el IDE

Escribe tu solución directamente en el editor y pulsa Ejecutar: el programa se compila y se prueba con varios casos de prueba, algunos ocultos. Tu programa lee los datos de la entrada estándar con Scanner, así que funciona con cualquier valor.

☕JavaNúmeros primos con una funciónMedio

Completa la función esPrimo(int n) y úsala para mostrar, separados por espacios, todos los números primos desde 2 hasta el número leído.

⏳
Primos hasta 10
⏳
Primos hasta 20
⏳
Test oculto #3
0/3 tests pasados
☕JavaFactorial recursivoMedio

Implementa la función recursiva factorial(int n) que devuelva n! como long. El programa muestra 'n! = resultado'. Recuerda que 0! = 1.

⏳
5! = 120
⏳
0! = 1
⏳
Test oculto #3
0/3 tests pasados

¿Has encontrado un error, algo desactualizado o una explicación que no se entiende? Avísanos y lo corregimos.

¿Has terminado este tema?

Crea una cuenta gratis para guardar qué temas has terminado, subir de nivel y ganar medallas.

Guardar mi progreso