Tecnicas de JavaJava Logo

Explora los conceptos de Java con un enfoque en las buenas prácticas y patrones de diseño.

Datos Enumerados (Enum)

Los enums en Java son tipos especiales que representan un grupo fijo de constantes. Son perfectos para representar estados, categorías o cualquier conjunto predefinido de valores.

Enum con Estados de Proceso y Métodos

java
1public enum EstadoProceso {
2    PENDIENTE("En espera de ejecución"),
3    EJECUTANDO("Proceso en curso"),
4    COMPLETADO("Finalizado exitosamente"),
5    ERROR("Falló durante la ejecución");
6    
7    private final String descripcion;
8    
9    EstadoProceso(String descripcion) {
10        this.descripcion = descripcion;
11    }
12    
13    public String getDescripcion() {
14        return descripcion;
15    }
16    
17    public boolean puedeCancelar() {
18        return this == PENDIENTE || this == EJECUTANDO;
19    }
20    
21    public boolean esFinal() {
22        return this == COMPLETADO || this == ERROR;
23    }
24    
25    // Uso de values() y valueOf()
26    public static void mostrarEstados() {
27        for (EstadoProceso estado : EstadoProceso.values()) {
28            System.out.println(estado.name() + ": " + estado.getDescripcion());
29        }
30    }
31}
32
33// Uso en aplicación
34public class GestorProcesos {
35    public static void main(String[] args) {
36        EstadoProceso estado = EstadoProceso.PENDIENTE;
37        System.out.println("Estado: " + estado);
38        System.out.println("Descripción: " + estado.getDescripcion());
39        System.out.println("¿Puede cancelar?: " + estado.puedeCancelar());
40        
41        // Conversión desde String
42        EstadoProceso estadoDesdeTexto = EstadoProceso.valueOf("COMPLETADO");
43        System.out.println("Estado desde texto: " + estadoDesdeTexto);
44        
45        // Mostrar todos los estados
46        EstadoProceso.mostrarEstados();
47    }
48}

Enum con Implementación de Interface

java
1// Interface para operaciones matemáticas
2public interface Operacion {
3    double aplicar(double a, double b);
4}
5
6// Enum que implementa la interface
7public enum OperacionAritmetica implements Operacion {
8    SUMA {
9        public double aplicar(double a, double b) {
10            return a + b;
11        }
12    },
13    RESTA {
14        public double aplicar(double a, double b) {
15            return a - b;
16        }
17    },
18    MULTIPLICACION {
19        public double aplicar(double a, double b) {
20            return a * b;
21        }
22    },
23    DIVISION {
24        public double aplicar(double a, double b) {
25            if (b == 0) throw new ArithmeticException("División por cero");
26            return a / b;
27        }
28    };
29    
30    // Método estático para buscar operación
31    public static OperacionAritmetica desdeSimbolo(String simbolo) {
32        return switch(simbolo) {
33            case "+" -> SUMA;
34            case "-" -> RESTA;
35            case "*" -> MULTIPLICACION;
36            case "/" -> DIVISION;
37            default -> throw new IllegalArgumentException("Símbolo no válido: " + simbolo);
38        };
39    }
40}
41
42// Uso en calculadora
43public class Calculadora {
44    public static void main(String[] args) {
45        double x = 10, y = 3;
46        
47        for (OperacionAritmetica op : OperacionAritmetica.values()) {
48            try {
49                double resultado = op.aplicar(x, y);
50                System.out.printf("%s: %.2f %s %.2f = %.2f%n", 
51                    op.name(), x, op.name().charAt(0), y, resultado);
52            } catch (ArithmeticException e) {
53                System.out.println("Error en " + op.name() + ": " + e.getMessage());
54            }
55        }
56        
57        // Uso con símbolo
58        OperacionAritmetica operacion = OperacionAritmetica.desdeSimbolo("*");
59        System.out.println("Resultado: " + operacion.aplicar(5, 4));
60    }
61}

Formato de Datos

El formateo de datos es esencial para presentar información de manera legible y profesional. Java ofrece múltiples herramientas para formatear strings, números y fechas.

Formato Avanzado con Locale y Especificadores

java
1import java.text.DecimalFormat;
2import java.text.NumberFormat;
3import java.time.LocalDateTime;
4import java.time.format.DateTimeFormatter;
5import java.util.Locale;
6
7public class FormatoAvanzado {
8    public static void main(String[] args) {
9        // Formato numérico con diferentes locales
10        double cantidad = 1234567.8912;
11        NumberFormat[] formateadores = {
12            NumberFormat.getNumberInstance(Locale.US),      // 1,234,567.891
13            NumberFormat.getNumberInstance(Locale.GERMANY), // 1.234.567,891
14            NumberFormat.getNumberInstance(new Locale("es", "NI")) // 1,234,567.891
15        };
16        
17        System.out.println("=== FORMATO NUMÉRICO INTERNACIONAL ===");
18        for (NumberFormat nf : formateadores) {
19            nf.setMaximumFractionDigits(3);
20            System.out.println(nf.format(cantidad));
21        }
22        
23        // Formato de moneda
24        NumberFormat monedaUS = NumberFormat.getCurrencyInstance(Locale.US);
25        NumberFormat monedaLocal = NumberFormat.getCurrencyInstance(new Locale("es", "NI"));
26        System.out.println("\n=== FORMATO MONEDA ===");
27        System.out.println("USD: " + monedaUS.format(cantidad));
28        System.out.println("Local: " + monedaLocal.format(cantidad));
29        
30        // Formato de fecha y hora avanzado
31        LocalDateTime ahora = LocalDateTime.now();
32        DateTimeFormatter[] formatosFecha = {
33            DateTimeFormatter.ISO_LOCAL_DATE_TIME,
34            DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss"),
35            DateTimeFormatter.ofPattern("EEEE, d 'de' MMMM 'de' yyyy", new Locale("es", "NI")),
36            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")
37        };
38        
39        System.out.println("\n=== FORMATOS DE FECHA ===");
40        for (DateTimeFormatter formato : formatosFecha) {
41            System.out.println(ahora.format(formato));
42        }
43        
44        // Formato printf avanzado para reportes
45        System.out.println("\n=== REPORTE TABULAR ===");
46        System.out.printf("%-15s %-10s %12s %12s%n", "Producto", "Categoría", "Precio", "Stock");
47        System.out.println("─".repeat(55));
48        System.out.printf("%-15s %-10s %12.2f %12d%n", "Laptop Dell", "Tecnología", 899.99, 15);
49        System.out.printf("%-15s %-10s %12.2f %12d%n", "Mouse Logi", "Accesorios", 25.50, 100);
50        System.out.printf("%-15s %-10s %12.2f %12d%n", "Monitor 24'", "Tecnología", 299.999, 8);
51        
52        // Formato científico y porcentaje
53        System.out.println("\n=== FORMATOS ESPECIALES ===");
54        double velocidad = 299792458;
55        double tasa = 0.075;
56        System.out.printf("Velocidad luz: %e m/s%n", velocidad);
57        System.out.printf("Velocidad luz: %.3e m/s%n", velocidad);
58        System.out.printf("Tasa interés: %.2f%%%n", tasa * 100);
59        System.out.printf("Tasa interés: %.1f%%%n", tasa * 100);
60    }
61}

DecimalFormat y Parsing de Datos

java
1import java.text.DecimalFormat;
2import java.text.ParseException;
3import java.text.DecimalFormatSymbols;
4import java.util.Locale;
5
6public class FormatoDecimalAvanzado {
7    public static void main(String[] args) {
8        // Patrones complejos de DecimalFormat
9        double[] numeros = {1234.567, -9876.543, 0.12345, 1234567.89};
10        
11        String[] patrones = {
12            "#,##0.00",
13            "¤#,##0.00;(¤#,##0.00)",
14            "0.###E0",
15            "#,##0.00%",
16            "###,##0.###"
17        };
18        
19        System.out.println("=== FORMATOS DECIMALES AVANZADOS ===");
20        for (String patron : patrones) {
21            DecimalFormat df = new DecimalFormat(patron);
22            System.out.println("\nPatrón: " + patron);
23            for (double num : numeros) {
24                System.out.printf("  %12.3f -> %s%n", num, df.format(num));
25            }
26        }
27        
28        // DecimalFormat con símbolos personalizados
29        DecimalFormatSymbols simbolosPersonalizados = new DecimalFormatSymbols();
30        simbolosPersonalizados.setDecimalSeparator(',');
31        simbolosPersonalizados.setGroupingSeparator('.');
32        simbolosPersonalizados.setMinusSign('–');
33        
34        DecimalFormat dfPersonalizado = new DecimalFormat("#,##0.00", simbolosPersonalizados);
35        System.out.println("\n=== FORMATO PERSONALIZADO ===");
36        System.out.println("Número formateado: " + dfPersonalizado.format(-1234567.891));
37        
38        // Parsing de strings numéricos
39        System.out.println("\n=== PARSING DE DATOS ===");
40        String[] entradas = {"1.234,56", "–987.654,32", "45%", "1.234E2"};
41        
42        DecimalFormat[] parsers = {
43            new DecimalFormat("#,##0.00", simbolosPersonalizados),
44            new DecimalFormat("#,##0.00", DecimalFormatSymbols.getInstance(Locale.US)),
45            new DecimalFormat("0%"),
46            new DecimalFormat("0.###E0")
47        };
48        
49        for (String entrada : entradas) {
50            for (int i = 0; i < parsers.length; i++) {
51                try {
52                    Number numero = parsers[i].parse(entrada);
53                    System.out.printf("Entrada: %-12s -> Parser %d: %f%n", 
54                        entrada, i + 1, numero.doubleValue());
55                    break;
56                } catch (ParseException e) {
57                    // Continuar con el siguiente parser
58                }
59            }
60        }
61        
62        // Formato para números muy grandes y muy pequeños
63        System.out.println("\n=== NÚMEROS EXTREMOS ===");
64        double[] extremos = {Double.MAX_VALUE, Double.MIN_VALUE, 0.000000123, 123456789012345.67};
65        DecimalFormat dfCientifico = new DecimalFormat("0.#####E0");
66        
67        for (double extremo : extremos) {
68            System.out.printf("Original: %e -> Formateado: %s%n", 
69                extremo, dfCientifico.format(extremo));
70        }
71    }
72}

Números Aleatorios

La generación de números aleatorios es fundamental para simulaciones, juegos, testing y aplicaciones de seguridad. Java proporciona varias formas de generar números aleatorios.

Simulación con Random y Análisis Estadístico

java
1import java.util.Random;
2import java.util.Arrays;
3
4public class SimulacionEstadistica {
5    public static void main(String[] args) {
6        Random random = new Random(42); // Semilla fija para reproducibilidad
7        
8        // Simulación de lanzamiento de dados
9        System.out.println("=== SIMULACIÓN DE DADOS ===");
10        int[] frecuenciaDados = new int[13]; // 2-12 para dos dados
11        
12        for (int i = 0; i < 10000; i++) {
13            int dado1 = random.nextInt(6) + 1;
14            int dado2 = random.nextInt(6) + 1;
15            int suma = dado1 + dado2;
16            frecuenciaDados[suma]++;
17        }
18        
19        System.out.println("Resultados de 10,000 lanzamientos:");
20        for (int i = 2; i <= 12; i++) {
21            System.out.printf("Suma %2d: %4d veces (%.1f%%)%n", 
22                i, frecuenciaDados[i], (frecuenciaDados[i] / 10000.0) * 100);
23        }
24        
25        // Generación de números con distribución normal (Gaussiana)
26        System.out.println("\n=== DISTRIBUCIÓN NORMAL ===");
27        double[] muestras = new double[1000];
28        double suma = 0;
29        
30        for (int i = 0; i < muestras.length; i++) {
31            muestras[i] = random.nextGaussian() * 15 + 100; // μ=100, σ=15
32            suma += muestras[i];
33        }
34        
35        double media = suma / muestras.length;
36        System.out.printf("Media: %.2f%n", media);
37        
38        // Simulación de Monte Carlo para π
39        System.out.println("\n=== SIMULACIÓN DE MONTE CARLO (π) ===");
40        int puntosCirculo = 0;
41        int totalPuntos = 1000000;
42        
43        for (int i = 0; i < totalPuntos; i++) {
44            double x = random.nextDouble() * 2 - 1; // -1 to 1
45            double y = random.nextDouble() * 2 - 1; // -1 to 1
46            if (x * x + y * y <= 1) {
47                puntosCirculo++;
48            }
49        }
50        
51        double piAproximado = 4.0 * puntosCirculo / totalPuntos;
52        System.out.printf("π aproximado: %.6f (error: %.6f)%n", 
53            piAproximado, Math.abs(Math.PI - piAproximado));
54        
55        // Generación de datos de prueba
56        System.out.println("\n=== DATOS DE PRUEBA ALEATORIOS ===");
57        String[] nombres = {"Ana", "Carlos", "María", "José", "Laura", "Miguel"};
58        String[] departamentos = {"Ventas", "TI", "RH", "Finanzas", "Marketing"};
59        
60        for (int i = 0; i < 5; i++) {
61            String nombre = nombres[random.nextInt(nombres.length)];
62            String depto = departamentos[random.nextInt(departamentos.length)];
63            double salario = 1500 + random.nextDouble() * 3500; // 1500-5000
64            int antiguedad = random.nextInt(20) + 1;
65            
66            System.out.printf("Empleado %d: %-10s | %-10s | $%8.2f | %2d años%n",
67                i + 1, nombre, depto, salario, antiguedad);
68        }
69    }
70}

SecureRandom para Aplicaciones Seguras

java
1import java.security.SecureRandom;
2import java.util.Base64;
3import java.util.HexFormat;
4
5public class SeguridadCriptografica {
6    private static final String CARACTERES_SEGUROS = 
7        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+";
8    
9    public static void main(String[] args) {
10        SecureRandom secureRandom = new SecureRandom();
11        
12        // Generación de tokens de sesión
13        System.out.println("=== TOKENS DE SEGURIDAD ===");
14        for (int i = 0; i < 3; i++) {
15            byte[] token = new byte[32]; // 256 bits
16            secureRandom.nextBytes(token);
17            String tokenBase64 = Base64.getUrlEncoder().withoutPadding().encodeToString(token);
18            System.out.printf("Token %d: %s%n", i + 1, tokenBase64);
19        }
20        
21        // Generación de contraseñas seguras
22        System.out.println("\n=== CONTRASEÑAS SEGURAS ===");
23        for (int longitud : new int[]{12, 16, 20}) {
24            String contraseña = generarContraseñaSegura(secureRandom, longitud);
25            System.out.printf("Contraseña %d caracteres: %s%n", longitud, contraseña);
26        }
27        
28        // Claves criptográficas
29        System.out.println("\n=== CLAVES CRIPTOGRÁFICAS ===");
30        byte[] claveAES = new byte[32]; // AES-256
31        byte[] claveHMAC = new byte[64]; // HMAC-SHA512
32        byte[] iv = new byte[16]; // Vector de inicialización
33        
34        secureRandom.nextBytes(claveAES);
35        secureRandom.nextBytes(claveHMAC);
36        secureRandom.nextBytes(iv);
37        
38        HexFormat hex = HexFormat.of();
39        System.out.println("Clave AES-256: " + hex.formatHex(claveAES));
40        System.out.println("Clave HMAC: " + hex.formatHex(claveHMAC).substring(0, 32) + "...");
41        System.out.println("IV: " + hex.formatHex(iv));
42        
43        // Nonce para prevención de replay attacks
44        System.out.println("\n=== NONCE ÚNICOS ===");
45        for (int i = 0; i < 5; i++) {
46            byte[] nonce = new byte[8]; // 64 bits
47            secureRandom.nextBytes(nonce);
48            System.out.printf("Nonce %d: %s%n", i + 1, hex.formatHex(nonce));
49        }
50        
51        // Simulación de OTP (One-Time Password)
52        System.out.println("\n=== CÓDIGOS OTP ===");
53        for (int i = 0; i < 5; i++) {
54            String otp = generarOTP(secureRandom, 6);
55            System.out.printf("OTP %d: %s%n", i + 1, otp);
56        }
57    }
58    
59    private static String generarContraseñaSegura(SecureRandom sr, int longitud) {
60        StringBuilder sb = new StringBuilder(longitud);
61        for (int i = 0; i < longitud; i++) {
62            int index = sr.nextInt(CARACTERES_SEGUROS.length());
63            sb.append(CARACTERES_SEGUROS.charAt(index));
64        }
65        return sb.toString();
66    }
67    
68    private static String generarOTP(SecureRandom sr, int digitos) {
69        StringBuilder otp = new StringBuilder(digitos);
70        for (int i = 0; i < digitos; i++) {
71            otp.append(sr.nextInt(10)); // 0-9
72        }
73        return otp.toString();
74    }
75}

Recursividad

La recursividad es una técnica donde una función se llama a sí misma para resolver un problema dividiéndolo en subproblemas más pequeños. Es elegante pero requiere cuidado para evitar desbordamientos de pila.

Algoritmos de Ordenamiento Recursivos

java
1import java.util.Arrays;
2
3public class OrdenamientoRecursivo {
4    
5    // QuickSort recursivo
6    public static void quickSort(int[] arr, int low, int high) {
7        if (low < high) {
8            int pi = particion(arr, low, high);
9            quickSort(arr, low, pi - 1);  // Ordenar elementos antes del pivote
10            quickSort(arr, pi + 1, high); // Ordenar elementos después del pivote
11        }
12    }
13    
14    private static int particion(int[] arr, int low, int high) {
15        int pivot = arr[high];
16        int i = low - 1;
17        
18        for (int j = low; j < high; j++) {
19            if (arr[j] <= pivot) {
20                i++;
21                intercambiar(arr, i, j);
22            }
23        }
24        intercambiar(arr, i + 1, high);
25        return i + 1;
26    }
27    
28    // MergeSort recursivo
29    public static void mergeSort(int[] arr, int left, int right) {
30        if (left < right) {
31            int mid = left + (right - left) / 2;
32            mergeSort(arr, left, mid);      // Ordenar primera mitad
33            mergeSort(arr, mid + 1, right); // Ordenar segunda mitad
34            merge(arr, left, mid, right);   // Combinar mitades ordenadas
35        }
36    }
37    
38    private static void merge(int[] arr, int left, int mid, int right) {
39        int n1 = mid - left + 1;
40        int n2 = right - mid;
41        
42        int[] L = new int[n1];
43        int[] R = new int[n2];
44        
45        System.arraycopy(arr, left, L, 0, n1);
46        System.arraycopy(arr, mid + 1, R, 0, n2);
47        
48        int i = 0, j = 0, k = left;
49        while (i < n1 && j < n2) {
50            if (L[i] <= R[j]) {
51                arr[k++] = L[i++];
52            } else {
53                arr[k++] = R[j++];
54            }
55        }
56        
57        while (i < n1) arr[k++] = L[i++];
58        while (j < n2) arr[k++] = R[j++];
59    }
60    
61    // Búsqueda binaria recursiva
62    public static int busquedaBinaria(int[] arr, int objetivo, int left, int right) {
63        if (left > right) return -1; // Caso base: no encontrado
64        
65        int mid = left + (right - left) / 2;
66        
67        if (arr[mid] == objetivo) {
68            return mid; // Caso base: encontrado
69        } else if (arr[mid] > objetivo) {
70            return busquedaBinaria(arr, objetivo, left, mid - 1); // Buscar en izquierda
71        } else {
72            return busquedaBinaria(arr, objetivo, mid + 1, right); // Buscar en derecha
73        }
74    }
75    
76    // Fibonacci con memoización para optimización
77    private static long[] memoFibonacci = new long[100];
78    
79    public static long fibonacci(int n) {
80        if (n <= 1) return n; // Caso base
81        
82        if (memoFibonacci[n] != 0) {
83            return memoFibonacci[n]; // Retornar valor ya calculado
84        }
85        
86        memoFibonacci[n] = fibonacci(n - 1) + fibonacci(n - 2);
87        return memoFibonacci[n];
88    }
89    
90    private static void intercambiar(int[] arr, int i, int j) {
91        int temp = arr[i];
92        arr[i] = arr[j];
93        arr[j] = temp;
94    }
95    
96    public static void main(String[] args) {
97        int[] datos = {64, 34, 25, 12, 22, 11, 90, 5, 77, 30};
98        int[] copia1 = Arrays.copyOf(datos, datos.length);
99        int[] copia2 = Arrays.copyOf(datos, datos.length);
100        
101        System.out.println("Array original: " + Arrays.toString(datos));
102        
103        // QuickSort
104        quickSort(copia1, 0, copia1.length - 1);
105        System.out.println("QuickSort: " + Arrays.toString(copia1));
106        
107        // MergeSort
108        mergeSort(copia2, 0, copia2.length - 1);
109        System.out.println("MergeSort: " + Arrays.toString(copia2));
110        
111        // Búsqueda binaria
112        int objetivo = 25;
113        int indice = busquedaBinaria(copia1, objetivo, 0, copia1.length - 1);
114        System.out.printf("Búsqueda binaria de %d: índice %d%n", objetivo, indice);
115        
116        // Fibonacci con memoización
117        System.out.println("\nFibonacci con memoización:");
118        for (int i = 0; i <= 20; i++) {
119            System.out.printf("F(%2d) = %d%n", i, fibonacci(i));
120        }
121    }
122}

Problemas Clásicos de Recursividad

java
1import java.io.File;
2import java.util.ArrayList;
3import java.util.List;
4
5public class ProblemasRecursivos {
6    
7    // Torres de Hanoi
8    public static void torresHanoi(int n, char origen, char destino, char auxiliar) {
9        if (n == 1) {
10            System.out.println("Mover disco 1 de " + origen + " a " + destino);
11            return;
12        }
13        torresHanoi(n - 1, origen, auxiliar, destino);
14        System.out.println("Mover disco " + n + " de " + origen + " a " + destino);
15        torresHanoi(n - 1, auxiliar, destino, origen);
16    }
17    
18    // Permutaciones de un string
19    public static List<String> generarPermutaciones(String str) {
20        List<String> resultado = new ArrayList<>();
21        generarPermutaciones("", str, resultado);
22        return resultado;
23    }
24    
25    private static void generarPermutaciones(String prefijo, String sufijo, List<String> resultado) {
26        if (sufijo.length() == 0) {
27            resultado.add(prefijo); // Caso base: permutación completa
28            return;
29        }
30        
31        for (int i = 0; i < sufijo.length(); i++) {
32            String nuevoPrefijo = prefijo + sufijo.charAt(i);
33            String nuevoSufijo = sufijo.substring(0, i) + sufijo.substring(i + 1);
34            generarPermutaciones(nuevoPrefijo, nuevoSufijo, resultado);
35        }
36    }
37    
38    // Recorrido recursivo de directorios
39    public static void listarArchivos(File directorio, String nivel) {
40        if (!directorio.exists() || !directorio.isDirectory()) {
41            return;
42        }
43        
44        File[] archivos = directorio.listFiles();
45        if (archivos == null) return;
46        
47        for (File archivo : archivos) {
48            System.out.println(nivel + archivo.getName());
49            if (archivo.isDirectory()) {
50                listarArchivos(archivo, nivel + "  "); // Llamada recursiva para subdirectorios
51            }
52        }
53    }
54    
55    // Problema de las N-Reinas
56    public static void resolverNReinas(int n) {
57        int[] tablero = new int[n];
58        System.out.println("\nSoluciones para " + n + " reinas:");
59        colocarReina(tablero, 0, n);
60    }
61    
62    private static void colocarReina(int[] tablero, int fila, int n) {
63        if (fila == n) {
64            imprimirTablero(tablero); // Caso base: solución encontrada
65            return;
66        }
67        
68        for (int col = 0; col < n; col++) {
69            if (esSeguro(tablero, fila, col)) {
70                tablero[fila] = col;
71                colocarReina(tablero, fila + 1, n); // Llamada recursiva para siguiente fila
72            }
73        }
74    }
75    
76    private static boolean esSeguro(int[] tablero, int fila, int col) {
77        for (int i = 0; i < fila; i++) {
78            // Misma columna o misma diagonal
79            if (tablero[i] == col || Math.abs(tablero[i] - col) == Math.abs(i - fila)) {
80                return false;
81            }
82        }
83        return true;
84    }
85    
86    private static void imprimirTablero(int[] tablero) {
87        for (int fila : tablero) {
88            for (int col = 0; col < tablero.length; col++) {
89                System.out.print(fila == col ? "Q " : ". ");
90            }
91            System.out.println();
92        }
93        System.out.println();
94    }
95    
96    // Recursión indirecta ejemplo
97    public static void funcionA(int n) {
98        if (n <= 0) return;
99        System.out.println("A: " + n);
100        funcionB(n - 1); // Llamada a otra función que llama de vuelta
101    }
102    
103    public static void funcionB(int n) {
104        if (n <= 0) return;
105        System.out.println("B: " + n);
106        funcionA(n - 2); // Llamada recursiva indirecta
107    }
108    
109    public static void main(String[] args) {
110        System.out.println("=== TORRES DE HANOI ===");
111        torresHanoi(3, 'A', 'C', 'B');
112        
113        System.out.println("\n=== PERMUTACIONES ===");
114        List<String> permutaciones = generarPermutaciones("ABC");
115        System.out.println("Permutaciones de ABC: " + permutaciones);
116        
117        System.out.println("\n=== RECORRIDO DE DIRECTORIOS ===");
118        listarArchivos(new File("."), "");
119        
120        System.out.println("\n=== N-REINAS ===");
121        resolverNReinas(4);
122        
123        System.out.println("=== RECURSIÓN INDIRECTA ===");
124        funcionA(5);
125    }
126}