Print Friendly, PDF & Email

¿Cómo hacer expresiones lambda?

Los lambdas o closures son funciones anónimas, es decir que no necesitan una clase,  introducidas en Java 8 que nos permiten reducir tanto al verbosidad de nuestro código como mejorar el procesamiento de datos en grandes cantidades.

Si deseas conocer lo esencial de los lambdas te recomiendo este artículo en castellano, pero para no dejarte en ascuas te diré que en Java 8 podemos usar una expresión lambda de la siguiente forma:

expresiones lambda

con la ventaja de que los parámetros no son estrictamente tipados, permitiéndonos reducir la verbosidad del código, e incluso ganar elegancia en las soluciones.

En esta ocasión, he querido generar secuencias infinitas y finitas mediante el uso de estas expresiones lambdas, a continuación presentaré algunas de ellas, esperando que la forma de código sea suficientemente clara, pues en Java no existe la palabra reservada yield que en otros lenguajes nos ayuda a crear generadores.

¿Qué es un generador? ( Algo de teoría )

Conjunto generador es un concepto matemático de la teoria de conjuntos dónde esencialmente se generan conjuntos más grandes a partir de un conjunto, de manera similar en programación un generador es una rutina que nos permite controlar el comportamiento iterativo de un ciclo de manera conveniente para generar una secuencia de valores basada en alguna progresión matemática.

Para su implementación en Java es importante hacer notar que todos los generadores son iteradores, aunque se comportan como funciones. Todos los generadores son iteradores, pero no todos los iteradores son generadores, pues algunas secuencias no pueden ser moldeadas mediante funciones ( al respecto sugiero leer algo de fractales y teoría del caos ) .

Al grano, hacer un generador con una expresión lambda

Empecemos con algo simple, una función que nos genere números aleatorios infinitamente en el rango del 0 al 100.

Generador de números aleatorios infinitos

Para expresar esto con una expresión lambda escribimos nuestra función generadora fácilmente usando el muy conocido método random de la clase Math.

() -> (int) (Math.random() * 100)

para poder recibir un flujo de enteros a partir de esta función naturalmente lo haremos mediante la clase IntStream, que en su método generar (generate) recibe como parámetro una interfaz funcional, que para efectos prácticos se puede decir que casi siempre es un lambda.

El pequeño inconveniente con los streams es que a pesar de ser infinitos, para poder regresar resultados deben de usar un colector de datos que normalmente los cuantifica.

Para sortear ese pequeño inconveniente lo que haremos es a partir del flujo obtener un iterador, pues debido a que las interfaces funcionales proveen un método next podemos llamar este método desde el método next del iterador para un funcionamiento en caja negra, he aqui el código de esto ya con nuestra clase Generador:

public class Generator {
    private final Iterator iterator;

    public Generator(IntSupplier lambda) {
        IntStream i = IntStream.generate(
                lambda);
        iterator = i.iterator();
    }
}

Para acabar este primer ejemplo pongámoslo en un main y veamos el milagro hecho realidad, pues podemos agregar o quitar cuantas llamadas a next queramos apra obtener la cantidad de numeros que deseemos.

public class Generator {
    private final Iterator iterator;

    public Generator(IntSupplier lambda) {
        IntStream i = IntStream.generate(
                lambda);
        iterator = i.iterator();
    }

    public String next() {
            return iterator.next()   "";
    }

    public static void main(String[] args) {
        System.out.println("Generador aleatorio");
        Generator generatorRandom = new Generator(
                () -> (int) (Math.random() * 100)
        );
        imprimeIteraciones(generatorRandom);
    }

    private static void imprimeIteraciones(Generator incremento) {
        System.out.println(incremento.next());
        System.out.println(incremento.next());
        System.out.println(incremento.next());
        System.out.println(incremento.next());
        System.out.println(incremento.next());
        System.out.println(incremento.next());
        System.out.println(incremento.next());
        System.out.println(incremento.next());
    }
}

Generador de secuencia básica

Nuestro siguiente generador será para generar una secuencia que vaya desde el 0 hasta el inifinito aumentando de uno en uno. Como antes haremos nuestro lambda, pero como primer dificultad, dentro de un lambda únicamente se pueden llamar variables estáticas, por lo que ponerle una instancia de int sería problemático y sería un desperdicio de recursos tener una variable estática a nivel de clase para cada caso, así que usaremos un objeto especial llamando a un entero atómico, es decir, a un entero que puede realizar sobre si mismo operaciones atómicas( comunes ) simples, y guardar su valor internamente, para no tener que cambiar su valor nosotros cada vez que lo llamemos.

Nuestra expresión lambda queda así:

()->{new AtomicInteger()::getAndIncrement}

y aquí es donde la mágia sucede, la primera vez que se llame creará un nuevo objeto con valor cero y regresará su valor, pero a partir de la segunda vez, regresará el valor del entero y posteriormente incrementará el valor interno sin volver a crear el objeto.

de manera similar al anterior generador llamamos a este mediante

Generator incremento = new Generator(new AtomicInteger()::getAndIncrement);
imprimeIteraciones(incremento);

Generador con función personalizada

El anterior ejemplo nos plantea otra dificultad, si queremos usar el entero atómico para una función más compleja, para ello podemos usar variables dentro del lambda.

Por ejemplo un lambda para llamar a una función factorial quedaría algo así:

() -> {
         int n = a.getAndIncrement();
         int resultado = factorial(n);
         System.out.println("factorial("   n   ") = "   resultado);
         return resultado;
}

podemos definir nuestra función factorial de manera iterativa

private static int factorial(int n) {
        long r = 1;
        for (int i = 1; i <= n; i  ) {
            r *= i;
        }
        return new Long(r).intValue();
}

o de manera recursiva

private static int factorialR(int n) {
        if (n < 2) {
            return 1;
        }
        return n * factorialR(n - 1);
}

sinque esto afecte al generador

Generador finito

Un generador también debe de tener la capacidad de iterar sobre datos finitos, para ello, deberá de poder manejar aquellos casos en que el iterador haya terminado de producir resultados.

Esta vez, en vez de un lambda le pasaremos directamente un Stream de tipo IntStream

System.out.println("Generador de arreglo");
Generator generatorArray = new Generator(IntStream.of(1, 2, 3, 4, 5, 6));

y modificaremos el método next para que cuando no haya más resultados nos regrese la cadena no hay más resultados.

public String next() {
        if (iterator.hasNext()) {
            return iterator.next()   "";
        }
        return "no hay más resultados";
}

Generador híbrido

Tambien podemos modificar la lógica para que en cada caso se delegue dentro a la función correspondiente y nos regrese una secuencia basada en más de una funcion, por ejemplo para hacer una secuencia en la que los primeros 4 resultados sean parte del factorial y los demás sean el valor incremental podemos definir nuestro lambda así

() -> {
            int n = b.getAndIncrement();
            if (n < 4) {
                int resultado = factorial(n);
                return resultado;
            } else {
                return n;
            }
}

Generador de correos electrónicos

Aplicando lo anterior, podemos generalizar un poco más el código del genrador para generar correos electrónicos aleatorios válidos en vez de únicamente números con el siguiente lambda

() -> {
            return randomString(10)   "@"   randomString(6)   "."   randomString(3);
}

Este es el código completo:

import java.security.SecureRandom;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * Created by Javatlacati on 21/09/2016.
 */
public class Generator {
    private final Iterator iterator;

    public Generator(IntSupplier lambda) {
        IntStream i = IntStream.generate(
                lambda);
        iterator = i.iterator();
    }

    public Generator(IntStream i) {
        iterator = i.iterator();
    }

    public Generator(Supplier<String> lambda) {
        Stream<String> i = Stream.generate(lambda);
        iterator = i.iterator();
    }

    public String next() {
        if (iterator.hasNext()) {
            return iterator.next()   "";
        }
        return "no more results";
    }

    private static int factorial(int n) {
        long r = 1;
        for (int i = 1; i <= n; i  ) {
            r *= i;
        }
        return new Long(r).intValue();
    }

    private static int factorialR(int n) {
        if (n < 2) {
            return 1;
        }
        return n * factorialR(n - 1);
    }

    static String randomString(int len) {
        String AB = "0123456789abcdefghijklmnopqrstuvwxyz";
        SecureRandom rnd = new SecureRandom();
        StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; i  ) {
            sb.append(AB.charAt(rnd.nextInt(AB.length())));
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        System.out.println("Generador incremental");
        Generator incremento = new Generator(new AtomicInteger()::getAndIncrement);
        imprimeIteraciones(incremento);
        System.out.println("Generador aleatorio");
        Generator generatorRandom = new Generator(
                () -> (int) (Math.random() * 100)
        );
        imprimeIteraciones(generatorRandom);
        System.out.println("Generador factorial");
        AtomicInteger a = new AtomicInteger();
        Generator generatorFib = new Generator(
                () -> {
                    int n = a.getAndIncrement();
                    int resultado = factorial(n);
                    System.out.println("factorial("   n   ") = "   resultado);
                    return resultado;
                }
        );
        imprimeIteraciones(generatorFib);
        System.out.println("Generador factorial recursivo");
        AtomicInteger f = new AtomicInteger();
        Generator generatorFac = new Generator(
                () -> {
                    int n = f.getAndIncrement();
                    int resultado = factorialR(n);
                    System.out.println("factorial("   n   ") = "   resultado);
                    return resultado;
                }
        );
        imprimeIteraciones(generatorFac);
        System.out.println("Generador factorial recursivo");
        AtomicInteger g = new AtomicInteger();
        Generator generatorRFac = new Generator(
                () -> factorialR(g.getAndIncrement())
        );
        imprimeIteraciones(generatorRFac);
        System.out.println("Generador con condiciones");
        AtomicInteger b = new AtomicInteger();
        Generator generatorCondition = new Generator(() -> {
            int n = b.getAndIncrement();
            if (n < 4) {
                int resultado = factorial(n);
                return resultado;
            } else {
                return n;
            }
        });
        imprimeIteraciones(generatorCondition);
        System.out.println("Generador de arreglo");
        Generator generatorArray = new Generator(IntStream.of(1, 2, 3, 4, 5, 6));
        imprimeIteraciones(generatorArray);
        System.out.println("Generador de correos");
        AtomicInteger c = new AtomicInteger();
        Generator generatorCadena = new Generator(() -> {
            return randomString(10)   "@"   randomString(6)   "."   randomString(3);
        });
        imprimeIteraciones(generatorCadena);
        
    }

    private static void imprimeIteraciones(Generator incremento) {
        System.out.println(incremento.next());
        System.out.println(incremento.next());
        System.out.println(incremento.next());
        System.out.println(incremento.next());
        System.out.println(incremento.next());
        System.out.println(incremento.next());
        System.out.println(incremento.next());
        System.out.println(incremento.next());
    }
}

Despedida

Espero te haya servido de ayuda, o al menos de distracción, yo disfruté mucho haciendo este tutorial y cualquier duda o comentario es bienvenido, pues como ya lo dicen no hay preguntas tontas, solamente hay tontos que no preguntan.

Las expresiones lambda recientemente están en auge, y no únicamente en Java. Espero que este artículo te haya ayudado a comprender los motivos.

Radio

Do NOT follow this link or you will be banned from the site!