Java Lambda PDF
Java Lambda PDF
● Currificación
● Funciones como parámetros
● Clases de tipos
● Evaluación perezosa
● Sin efectos laterales
Motivación:
int divide(int *array, int start, int end) { void quicksort(int *array, int start, int end)
int left; {
int right; int pivot;
int pivot; if (start < end) {
int temp; pivot = divide(array, start, end);
pivot = array[start]; quicksort(array, start, pivot - 1);
left = start; quicksort(array, pivot + 1, end);
right = end; }
while (left < right) { }
while (array[right] > pivot) {
right--;
}
while ((left < right) && (array[left] <= pivot)) {
left++;
}
if (left < right) {
temp = array[left];
array[left] = array[right];
array[right] = temp;
}
}
quicksort :: (Ord a) => [a] -> [a]
temp = array[right]; quicksort [] = []
array[right] = array[start];
array[start] = temp;
quicksort (p:xs) =
return right;} quicksort [a | a <- xs, a <= p] ++ [p] ++ quicksort [a | a <-xs, a > p]
Motivación
Java 1.8 permite escribir código como:
.filter(n -> n % 2 == 1)
.map(n -> n * n)
.reduce(0, Integer::sum);
Motivación
¿Cómo han sido encajados dentro del paradigma de Orientación a Objetos que
implementa Java?
Índice
- Java lambdas
- Java Streams
- Conclusiones
Java lambdas
¿Cuál es el tipo de las siguientes expresiones en Java?
person -> (if (person.age > 40) then return “Very old man...”;
@FunctionalInterface
interface StringToIntMapper {
}
Java lambdas
Para poder entenderlo mejor, debemos distinguir en Java dos tipos de expresiones:
- “Standalone expressions”
- “Poly expressions”
Java lambdas: “standalone expressions”
“Hello”
new ArrayList<String>();
new ArrayList<>();
De este modo, la expresión “(x, y) -> x + y” es una “poly expression” cuyo tipo será,
dependiendo del contexto:
Las expresiones lambdas son una “versión reducida” de las clases anónimas
En realidad, se pueden describir como “azúcar sintáctico” para clases anónimas que
- no tengan estado y
@Override
return str.length();
};
// Expresión lambda
// Error de compilación
// forma no válida
- un bloque
También podemos trabajar con expresiones más elaboradas a través de los bloques
(Person p) -> p.getAge() > 21 && p.getWeight() > 100 && "Chris".equals(p.getName())
Una interfaz funcional es simplemente una interfaz que tiene exactamente un método
abstracto (los métodos default, static y los públicos heredados de Object no cuentan)
Java contiene en su librería múltiples casos de SAM (Single Abstract Method) interfaces:
@FunctionalInterface
interface Comparator<T>
{
int compare (T o1, T o2);
}
Simplificación: https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html
Java lambdas: ¿y los tipos...?
c.¿?;
Java lambdas: ¿y los tipos...?
Una interfaz funcional por tanto contendrá el método propio de su interfaz, así como
copias abstractas de todos los métodos de la clase “Object”
Una expresión lambda puede ser usada en cualquier sitio que se espera una instancia
de una clase “Single Abstract Method” (ni siquiera de una @FunctionalInterface, que es
un tipo particular de SAM)
Las interfaces SAM que llevan la anotación @FunctionalInterface son las que Java
espera o recomienda que implementes por medio de lambdas
Java lambdas: ¿y los tipos...?
Contraejemplo:
interface Comparable<T>{
-> h1.getName().compareTo(h2.getName());
Java lambdas: target typing
Para que una expresión lambda sea asignable a una interfaz T:
Function<T,R> R apply(T t) Representa una función que coge un parámetro de tipo T y devuelve un resultado de tipo
R
BiFunction<T,U,R> R apply(T t, U u) Representa una función que coge dos parámetros de tipos T y U, y devuelve un resultado
de tipo R
Predicate<T> boolean test(T t) Dado un parámetro de tipo T, comprueba algo y devuelve un booleano
Consumer<T> void accept(T t) Representa una operación que toma un parámetro, opera con él para hacer algo y no
devuelve un resultado
UnaryOperator<T> T apply(T t) Hereda de Function<T,T>. Representa una función que coge un parámetro y devuelve un
resultado del mismo tipo
BinaryOperator<T> T apply(T t1, T t2) Hereda de BiFunction<T,T,T>. Representa una función que coge dos parámetros del
mismo tipo y devuelve un resultado del mismo tipo también
System.out.println(square.apply(4));
Java lambdas: ¿Funciones como parámetros?
Ejercicio: ordenar crecientemente un vector de enteros.
¿Y ordenarlo decrecientemente?
¿Y ordenarlo alfabéticamente?
Java lambdas: ¿Funciones como parámetros?
1: Crear una lambda que instancia de la interface Comparator sobre tipos genéricos,
para que Java en tiempo de ejecución determine el tipo:
listaInt.sort(comp);
Java lambdas: ¿Funciones como parámetros?
● Si quisiésemos en orden inverso: listaInt.sort(comp.reversed());
● Si quisiésemos ordenar una lista de doubles (o de Strings, o de cualquier cosa
Comparable), también funciona! Java se encarga de inferir tipos en tiempo de
ejecución.
listaDouble.sort(comp);
listaEmpleados.sort(Comparator.comparing(Empleado::getNombre));
listaEmpleados.sort(Comparator.comparing(Empleado::getSueldo));
listaEmpleados.sort(Comparator.comparing(Empleado::getEdad));
Java lambdas: variable capture
Como en el caso de clases anónimas, una expresión lambda puede acceder a variables
locales “de fuera” en dos casos:
1. Si son finales
2. Si no son finales, pero se han inicializado solo una vez.
@FunctionalInterface
interface Calculator{
long calculate(long x, long y);
}
Java lambdas: algunas desventajas...
Extraído de http://blog.takipi.com/the-dark-side-of-lambda-expressions-in-java-8/
Traza de la excepción (código sin usar lambdas, Java 6 y Java 7): 1 at Prueba.check(Prueba.java:19)
2 at Prueba.main (Prueba.java:34)
Java lambdas: algunas desventajas...
Extraído de http://blog.takipi.com/the-dark-side-of-lambda-expressions-in-java-8/
int sum = 0;
if (n % 2 == 1) {
int square = n * n;
}
Java Streams
La vida después (Java 1.8 - ...) de Streams:
.filter(n -> n % 2 == 1)
.map(n -> n * n)
.reduce(0, Integer::sum);
Java Streams
https://hackage.haskell.org/package/Stream-0.4.7.2/docs/src/Data-Stream.html
- Isabelle/HOLCF:
“domain (unsafe) 'a stream = scons (ft::'a) (lazy rt::"'a stream") (infixr "&&" 65)”
Java Streams
https://hackage.haskell.org/package/Stream-0.4.7.2/docs/Data-Stream.html#g:4
Java Streams
En jerga Java, una Stream es una interface (o mejor aún, una familia de interfaces)
https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
Raoul-Gabriel Urma
Java Streams
- Desde un Array:
https://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#stream-T:A-
Java Streams
https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#lines-java.nio.file.P
ath-
Java Streams
¿De dónde puedo sacar una stream en Java...? (6 / 6)
catch ( IOException ex ) { }
}
Java Streams
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.htm
l
Java Streams
- no son almacenadas
- pueden representar una secuencia infinita de elementos
- están diseñadas para realizar iteraciones internas (implícitas)
- pueden ser procesadas en paralelo “automágicamente”
- soportan programación funcional
- soportan operaciones “lazy”
- pueden ser ordenadas o desordenadas
- no pueden ser reusadas
Java Streams: no son almacenadas
.…;
Java Streams: pueden representar una
secuencia infinita de datos
- Se pueden generar streams a partir de funciones iteradoras, y por tanto dar lugar a
un stream infinito:
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#iterate-T-jav
a.util.function.UnaryOperator-
Hay un tipo específico “LongStream” que sería más adecuado para este caso de uso; lo evitamos por simplicidad
Java Streams: pueden representar una
secuencia infinita de datos
Stream.generate(Math::random)
.…;
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#generate-jav
a.util.function.Supplier-
Java Streams: están diseñadas para realizar
iteraciones internas
- ¿Dónde está el iterador externo (for, forEach...)? Las iteraciones sobre streams se
realizan de forma interna:
.filter(n -> n % 2 == 1)
.map(n -> n * n)
.reduce(0, Integer::sum);
Java Streams: están diseñadas para realizar
iteraciones internas
- Java ofrece un framework “Fork / Join” (en la JDK desde Java 1.7) para dividir
tareas en subtareas recursivamente y ejecutarlas en paralelo; su uso directo no es
trivial
(https://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html)
Java Streams: están diseñadas para realizar
iteraciones internas
En Java 1.8 podemos encontrar usos concretos del framework “Fork / Join” de manera
transparente al usuario:
- Sobre Array:
https://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#parallelSort-T:A-
- Sobre Streams: int sum = numbers.parallelStream()
.filter(n -> n % 2 == 1)
.map(n -> n * n)
.reduce(0, Integer::sum);
Java Streams: están diseñadas para realizar
iteraciones internas
Streams también ofrece la posibilidad de crear iteradores explícitos, que nunca debería
haber motivo para usar:
while(iterator.hasNext()) {
int n = iterator.next();
...
}
Java Streams: soportan programación
funcional
Una stream soporta dos tipos de operaciones:
- Operaciones intermedias
- Operaciones terminales
.filter(n -> n % 2 == 1)
.map(n -> n * n)
.reduce(0, Integer::sum);
Java Streams: soportan programación
funcional
Las operaciones sobre una Stream se combinan generando “stream pipelines”
- Una operación terminal (forEach, toArray, reduce, min, max, count, anyMatch...)
Java Streams: soportan programación
funcional
En realidad, cada operación de Stream lleva “asociado” su tipo:
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#distinct--
Las primeras solo requieren procesar la stream una única vez… así que ofrecen
mejores resultados al paralelizar
Java Streams: soportan programación
funcional
- Una vez que hayamos invocado a cualquier operación terminal sobre una stream,
la stream no puede ser reusada (ha sido “consumida”)
.sorted ()
.findFirst();
Java Streams: más difícil todavía...
.sorted ()
.findFirst();
… cuyo valor es un “contenedor” (como un Array, List, etc) con 0 (None) ó 1 elementos
Java Streams: más difícil todavía...
.sorted ()
.findFirst();
https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html
Java Streams: algo para matemáticos...
.filter (Pruebas::isPrime)
.skip(1797778)
.findFirst();
.sorted()
.filter (Pruebas::isPrime)
.skip(1797778)
.findFirst();
.filter (Pruebas::isPrime)
.skip(1797778)
.findFirst();
class Person{
...
Person ken = new Person(1, "Ken", Gender.MALE, LocalDate.of(1970, Month.MAY, 4), 6000.0);
...
return persons; }
}
Java Streams: agrupando datos (ejemplos)
Método forEach
Person.persons().stream()
.filter( Person::isFemale )
.stream()
.stream()
(Fallo de compilación...)
Java Streams: agrupando datos (ejemplos)
Método reduce (dos parámetros)
.stream()
.map(Person::getIncome)
.reduce(0.0, Double::sum);
.stream()
.map(Person::getIncome)
.reduce(0.0, Double::sum);
.stream()
.parallelstream()
El tercer parámetro se usa para combinar los resultados de los diferentes hilos:
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#reduce-U-java.util.funct
ion.BiFunction-java.util.function.BinaryOperator-
Java Streams: creando nuevas agrupaciones
.stream()
.map(Person::getName)
.stream()
.map(Person::getName)
.stream()
.map(Person::getName)
List<String> names = li
.stream()
.map(Person::getName)
Fallo de compilación!!!
Java Streams: creando nuevas agrupaciones
También se puede hacer más fácil; clase Collector (métodos toList, toSet...)
.stream()
.map(Person::getName)
.collect(Collectors.toList());
Java Streams: creando nuevas agrupaciones
.stream()
.collect(Collectors.toMap(Person::getName, Person::getGender));
Java Streams: creando nuevas agrupaciones
El siguiente enlace contiene ejemplos de algunas de las operaciones adicionales que se
pueden hacer por medio de Collectors:
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html
Java Streams: ¿y quién paraleliza...?
https://docs.oracle.com/javase/8/docs/api/java/util/Spliterator.html
Reglas generales:
- Al principio de la charla dijimos que las expresiones lambda solo pueden acceder a:
- atributos finales
- atributos efectivamente finales
Como el atributo final (o efectivamente final) no cambia de valor, todos los hilos comparten un
único valor
Java Streams: ¿y sobre la concurrencia?
¿Qué resultados obtenemos al ejecutar el siguiente código?
result [0] = 0;
LongStream.range(0, 1000).parallel()
}
Java Streams: ¿y sobre la concurrencia?
Por ejemplo, los siguientes…
parallel: 4978113844326184457
parallel: 5358490659060193433
parallel: -4385875300773810675
parallel: -772351014370484819
parallel: 228875638259599668
parallel: 6452004553751078580
parallel: 5726083396462237492
parallel: -8157228369161431244
Java Streams: ¿y sobre la concurrencia?
¿Cuál ha sido el problema...?
(en realidad, en Java podemos hacer constante una referencia a un objeto, pero no el objeto
apuntado por la misma)
Por tanto, podemos “saltarnos” el requisito de que la variable usada en la lambda expresión
(“result [0]”, en lugar de “result”) sea constante;
- cada hilo la modifica en un orden y con unos valores diferentes, dando lugar a resultados no
deterministas
Java Streams: ¿y sobre la concurrencia?
En realidad, poner efectos laterales en una expresión lambda debería estar prohibido
Java Streams: ¿y sobre la concurrencia?
Otras opciones… Hacerlo secuencial:
result [0] = 0;
LongStream.range(0, 1000)
}
Java Streams: ¿y sobre la concurrencia?
Otras opciones… Usar forEachOrdered:
result [0] = 0;
LongStream.range(0, 1000).parallel()
}
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#forEachOrdered-java.ut
il.function.Consumer-
Java Streams: ¿y sobre la concurrencia?
Opciones erróneas… no vale con usar “sorted()” (“forEach” no preserva el orden)
result [0] = 0;
LongStream.range(0, 1000).parallel().sorted()
}
Java Streams: ¿y sobre la concurrencia?
Opciones erróneas… ni si intentamos “colar” una variable de tipo básico como “final” (o
efectivamente “final”)
LongStream.range(0, 1000)
}
Java lambdas y streams: ¿limitaciones?
- Una de ellas, code debugging (ya mencionado)
- La siguiente serie de posts identifica algunas otras debilidades, especialmente desde el
punto de vista de la comparación con la Programación Funcional
- https://dzone.com/articles/whats-wrong-java-8-currying-vs
- https://dzone.com/articles/whats-wrong-java-8-part-ii
- https://dzone.com/articles/whats-wrong-java-8-part-iii
- https://dzone.com/articles/whats-wrong-java-8-part-iv
- https://dzone.com/articles/whats-wrong-java-8-part-v
- https://dzone.com/articles/whats-wrong-java-8-part-vi
- https://dzone.com/articles/whats-wrong-java-8-part-vii
Java lambdas y streams: ¿limitaciones?
Los siguientes artículos también apuntan algunas limitaciones relevantes de la tecnología
- http://blog.takipi.com/the-dark-side-of-lambda-expressions-in-java-8/
- http://blog.takipi.com/new-parallelism-apis-in-java-8-behind-the-glitz-and-glamour/
Java lambdas y streams: demo
- Java 8 ofrece herramientas para aumentar la sencillez (¿la calidad?) del código que
generamos
- Permite trabajar con funciones como parámetros...siempre que haya una interface detrás…
- Permite trabajar con “streams” de datos de forma perezosa
- Permite paralelizar, por medio de Fork/Join, el procesado de “streams”
Para realizar esta presentación hemos tomado parte de la estructura y de los ejemplos del texto
“Beginning Java 8 Language Features” de Kishori Sharan (Apress, 2014); lo puedes encontrar en la
BibUR en https://link.springer.com/book/10.1007%2F978-1-4302-6659-4