Este documento presenta una introducción a las expresiones lambda y el API Stream en Java 8. Incluye una breve reseña sobre Java, explicaciones sobre expresiones lambda, interfaces funcionales y métodos de referencia. También cubre conceptos del API Stream como operaciones intermedias y terminales, y provee ejemplos de cómo usar streams para filtrar, mapear y coleccionar datos de manera funcional. El documento concluye con consejos prácticos sobre el uso de streams e ideas para depurar código que los utilice.
KELA Presentacion Costa Rica 2024 - evento Protégeles
Java 8: Lambdas, Streams y API Stream
1. Java 8: Introducción a
Expresiones Lambdas y
API Stream
Eudris Cabrera
@eudriscabrera
26 Abril 2017, UASD,Santo Domingo, R. D.
2. Eudris Cabrera
Graduado de Ingeniería Telemática en la PUCMM.
Entusiasta de la tecnología y el software libre,
desarrollador Java, instructor ocasional (Java / Linux),
aficionado del béisbol/sóftbol y los bailes latinos.
En el tiempo libre, escribe artículos, realiza charlas,
participa en eventos tecnológicos y ayuda en la revisión
de documentación técnica en el mundo Java y el código
abierto.
3. Breve Reseña sobre Java
Expresiones Lambda
Introducción al Stream API
Conceptos Avanzados sobre
Lambda y API Stream
Consejos Prácticos
Agenda
4. ● Java fue inventado como un lenguaje orientado a
objeto.
● Utiliza una máquina virtual para su ejecución (JVM)
● La plataforma java está dividida en Java SE, Java
EE, Java ME y Javafx
● ~ 2005 con Java 5 se introducen cambios
significativos.
○ Genéricos
○ Loop Mejorado
○ Anotaciones
○ Enumeraciones
○ Mejora para la concurrencia (AtomicInteger)
Breve Reseña sobre Java
James Gosling
Creador de Java
en los años 90's
5. Popularidad del Lenguaje Java
TIOBE Index lo sitúa en el primer lugar para el mes de Abril del 2017.
http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html
Githut Info* lo sitúa en el segundo lugar para el mes de Abril del 2017, muy
cercano a Javascript, quien ocupa el primer lugar.
http://githut.info
*Githut Info es un espacio que muestra el uso de los lenguajes de programación dentro de la
plataforma GitHub.
6. Java SE 8
Liberado en marzo del 2014, es la
plataforma que incluye la actualización
más grande para el modelo de
programación Java desde el lanzamiento
de la plataforma en 1996.
Incluye nuevas funcionalidades e
incorpora las características de los
lenguaje funcionales.
7. Ejemplo
Antes de Java 8
public void printTeamsHasNotWonWorldSeries(List<Team> teams){
for (Team team : teams) {
if(!team.isHasWonWoldSeries()){
System.out.println(""+ team.toString());
}
}
}
Cuales desventajas presenta esta solución ?
8. Ejemplo
Solución usando clases anonimas
public void printTeamsWithPredicate(List<Team> teams, Predicate<Team> tester){
for (Team t : teams) {
if(tester.test(t)){
System.out.println(""+ t.toString());
}
}
}
Predicate<Team> hasNotWonWS = new Predicate<Team>() {
@Override
public boolean test(Team t) {
return !t.isHasWonWoldSeries();
}
};
printTeamsWithPredicate(teams, hasNotWonWS);
9. Funciones como entidades de primer nivel
La programación funcional establece que las funciones
(métodos) sean definidas como entidades de primer nivel
¿Qué
significa
eso?
Qué las funciones pueden ser usada de la misma forma que
usamos enteros (integers) o cadenas de caracteres(strings).
Se pueden pasar funciones como parámetros a otras funciones
y pueden ser retornada como resultado de otras funciones.
10. Ejemplo
Funciones como entidades de primer nivel
printTeamsWithPredicate(teams, (Team t) -> !t.isHasWonWoldSeries());
Ejemplo básico usando expresiones lambdas.
Nota:
Puede parecer bastante confuso en un principio, pero ya revisaremos los conceptos y cambios en el lenguaje que nos
permitirán escribir código Java de esa manera.
11. Expresiones lambdas
Una expresión lambda representa una
función anónima.
λx → x+x
Función anónima que toma un número x y devuelve el resultado x + x.
12. Desarrolló en los años 30's
el "Cálculo lambda", un
sistema formal en lógica
matemática diseñado para
investigar la definición de
función, la noción de
aplicación de funciones y la
recursión.
Alonzo Church
13. Una expresión lambda se compone de un conjunto de
parámetros, un operador lambda (->) y un cuerpo de la
función.
14. Sintaxis
() -> System.out.println("Hello Lambda")
x -> x + 10
(int x, int y) -> { return x + y; }
(String x, String y) -> x.length() – y.length()
(int a, int b) --> a + b
(int a) --> a + 1
(int a, int b) --> {
System.out.println(a + b);
return a + b;
}
() --> new ArrayList();
16. ● Java necesitaba cambios para simplificar la
codificación paralela.
● Es muy útil para evitar tener que escribir
métodos que sólo utilizamos una vez.
● Simplifica cómo pasar comportamiento como
un parámetro (podemos pasar expresiones
lambda a métodos como argumentos).
17. Interfaces Funcionales
En Java, una interfaz funcional es, básicamente, una interfaz con un
único método abstracto.
Este tipo de interfaces también son conocidos como tipos SAM
(Single Abstract Method).
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Runnable r = () -> System.out.println("Hello Lambda");
18. java.util.function
● Predicate<T> - Recibe un argumento tipo T y retorna un booleano.
● Consumer<T> - una acción que se realiza sobre un objeto
● Function<T,R> - a funcion que transforma un valor T a R
● Supplier<T> - provee una instancia de T (como un factory).
● UnaryOperator<T> - Forma especializada de Function. Recibe un
valor T como argumento y retorna del un valor del mismo tipo T.
● BinaryOperator<T> - Forma especializada de BiFunction. Recibe
dos argumentos y retorna un resultado, todos del mismo tipo.
19. Metodos Predeterminados("Default")
● Tradicionalmente, las interfaces no pueden tener definiciones de método
(sólo declaraciones).
● Métodos predeterminados (Default) permite realizar implementaciones por
defecto dentro de una interfaz.
public interface A {
default void foo() {
System.out.println("Calling A.foo()");
}
}
public class Clazz implements A {
}
20. Method References
Nos permiten utilizar un método como una expresión lambda.
Formato: target_reference::method_name
class Person {
private String name;
private int age;
public int getAge() {return this.age;}
public String getName() {return this.name;}
}
Person[] people = ...;
Comparator<Person> byName = Comparator.comparing(Person::getName);
Arrays.sort(people, byName);
21. Method References
(Tipos)
● Método estático (ClassName::methName)
● Un método de instancia de un objeto particular
(instanceRef::methName)
● Un método super de un objeto particular (super::methName)
● Un método de instancia de un objeto arbitrario de un tipo particular
(ClassName::methName)
● Un constructor de una clase (ClassName::new)
● Un constructor de un arreglo (TypeName[]::new)
22. Introducción al API Stream
Stream:
● Representa una abstracción para
especificar cálculos agregados
(acumulativos).
● Simplifica la descripción de los cálculos
agregados (laziness,paralelismo).
23. Elementos de un Stream
Un Stream se compone de 3 elementos:
● Un origen
● Cero o más operaciones intermedias
● Una operación final (salida o un resultado)
28. Ejemplo
Antes de Java 8
Collection<Team> worldSeriesWinners = new ArrayList<>();
for (Team team : teams) {
if (team.isHasWonWoldSeries()) {
worldSeriesWinners.add(team);
}
}
Java 8
Collection<Team> worldSeriesWinners = teams
.stream()
.filter(p -> p.isHasWonWoldSeries())
.collect(Collectors.toList());
29. Ejemplo
Procesar la siguiente lista para que solamente contenga cadena de caracteres impares y en letra minúscula.
List<String> list = Arrays.asList("BarCamp", "MongoDB", "10Gen", "TokuMX", "Nagios", "PUCMM", "Ruby", "JSON", "JSON");
Solución usando clase anónima
List<String> newList = list
.stream()
.filter(new Predicate<String>() {
@Override
public boolean test(String t) {
return (t.length() % 2)==1;
}
}).map(String::toLowerCase)
.collect(Collectors.toList());
Solución usando expresiones lambdas
List<String> newList = list
.stream()
.filter(s -> (s.length() % 2)==1)
.map(String::toLowerCase)
.collect(Collectors.toList());
34. Ejemplo
Creación de un Stream vacío
Stream<String> emptyStream = Stream.empty();
long val = emptyStream.count();
Suma de arreglo de enteros
int[] numbers = {1, 2, 3, 4, 5, 6, 7};
int sum = Arrays.stream(numbers).sum();
Convertir Stream a List
List<String> abc = Stream.of("a", "b", "c")
.collect(Collectors.toList());
Usando operación count
long count = Stream.of("one").count();
35. Ejemplo
Usando forEach(Consumer c)
List<Team> teams = new ArrayList<>();
teams.add(new Team("STL", "St. Louis Cardinals", true, 2011));
teams.add(new Team("NYM", "NY Mets", true, 1986));
teams.add(new Team("LAA", "LA Angels", true, 2002));
teams.add(new Team("WSN", "Washington Nationals", false, 0));
teams.add(new Team("LAD", "LA Dodgers", false, 0));
teams.forEach(p -> System.out.println(p));
36. Streams de objetos y tipos primitivos
● Java tiene valores primitivos, así como tipos de objetos.
● Para mejorar la eficiencia de Stream tenemos tres Stream de tipos primitivos
○ IntStream, DoubleStream, LongStream
● Pueden usarse junto a los métodos mapToInt(), mapToDouble(),
mapToLong()
38. Nuevos métodos útiles en Java 8
que pueden utilizar Lambdas
Iterable
Iterable.forEach(Consumer c)
Collection
Collection.removeIf(Predicate p)
List
List.replaceAll(UnaryOperator o)
List.sort(Comparator c)
Reemplaza "Collections.sort(List l, Comparator c)"
39. Ejemplo
Usando Collection.removeIf(Predicate p)
List<Person> personList = new ArrayList<>();
personList.add(new Person("Wilson",35));
personList.add(new Person("Peter",25));
personList.add(new Person("Jones",27));
Usando Predicate como una clase anónima
personList.removeIf(new Predicate<Person>() {
@Override
public boolean test(Person person) {
return "Jones".equals(person.getName());
}
});
Usando Predicate como expresion
Lambda.
1.
personList.removeIf((Person person) ->
"Jones".equals(person.getName()));
2.
personList.removeIf(person ->
"Jones".equals(person.getName()));
40. Ejemplo
Procesar la siguiente lista para que solamente contenga cadena con letra mayúscula.
Antes de Java 8
List<String> list = Arrays.asList("Alfa", "Bravo", "Charlie", "Delta", "Echo", "Golf");
List<String> listUC=Lists.newArrayList();
for (ListIterator<String> it = list.listIterator(); it.hasNext();) {
listUC.add(it.next().toUpperCase());
}
Usando List.replaceAll(UnaryOperator o)
list.replaceAll(String::toUpperCase);
Method References
41. Ejemplo
Ordenar la siguiente lista por nombre de personas.
List<Person> personList = new ArrayList<>();
personList.add(new Person("Wilson",35));
personList.add(new Person("Peter",25));
personList.add(new Person("Jones",27));
Java 8
Collections.sort(personList,
(Person p1, Person p2) -> p1.getName().compareTo(p2.getName()));
Usando el método por defecto
Comparator.comparing
1.
personList.sort(Comparator.comparing((Person p)
-> p.getName()));
2.
personList.sort(Comparator.comparing(p ->
p.getName()));
42. Ejemplo
Ordenar la siguiente lista por nombre de personas.
List<Person> personList = new ArrayList<>();
personList.add(new Person("Wilson",35));
personList.add(new Person("Peter",25));
personList.add(new Person("Jones",27));
Java 8
Comparación de dos niveles
personList.sort(Comparator.comparing(Person::getAge).thenComparing(Person::getName));
Usando import estático
personList.sort(comparing(Person::getAge).thenComparing(Person::getName));
methods references
43. Usando colectores (collectors)
List<String> teamsWithNull = Lists.newArrayList(null, "NY Mets", null,
"Washington Nationals",
"LA Angels", null);
List<String> filterStrings = teamsWithNull
.stream()
.filter(p -> p != null)
.collect(Collectors.toList());
Convertir a un List sin
valores nulos
44. Usando colectores (collectors)
Antes de Java 8
Map<String, Team> mappedTeams = new HashMap<>();
for (Team team : teams) {
mappedTeams.put(team.getTeamId(), team);
}
Java 8
Map<String, Team> mappedTeams = teams
.stream()
.collect(Collectors.toMap(Team::getTeamId, (p) -> p));
Convertir un List a un
Map
45. Depuración (Debugging)
Una forma de hacer depuración de aplicaciones que usen lambas y Streams es usando el método
+peek(Consumer<T>):Stream<T> , el cuál es una operación intermedia y por lo tanto no interrumpe el procesamiento del
Stream.
List<String> newList1 = list
.stream()
.filter(s -> (s.length() % 2)==1)
.map(String::toLowerCase)
.peek(System.out::println)
.collect(Collectors.toList());
Colocar un breakpoint
list
.stream()
.filter(s -> (s.length() % 2)==1)
.map(String::toLowerCase)
.peek(s -> s.toString())
.collect(Collectors.toList());
46. Depuración(Debugging)
Algunas veces hacer debugging con expresiones lambdas resulta ser un poco tedioso. Las expresiones lambdas no se
compilan como las clases anónimas, sino que son compiladas con llamadas a invokedynamic y su implementación se
hace en tiempo de ejecución.
Una solución alternativa a estos casos es la siguiente:
- Extraer el código de una expresión Lambda en un método separado.
- Sustituir la expresión Lambda con method reference por un nuevo método.
- Establecer puntos de interrupción en las sentencias en el nuevo método.
- Examinar el estado del programa utilizando depurador.
47. Consejos Prácticos
● Streams pueden ser infinitos, así como finitos. No existe el concepto de
"ruptura" de un Stream. Utilice la operación final (terminal) correspondiente
para detener el procesamiento O utilice el Stream de forma infinita.
● Evite forEach (excepto en casos especiales)
● Necesitas pensar en programación funcional más que programación
imperativa (trate de dejar de pensar en los bucles).
● Piense en la forma de abordar los problemas utilizando recursividad.
52. Asuntos Legales
Todas las marcas registradas, así como todos los
logotipos, imágenes, fotografías, audio y vídeos
mostrados en esta presentación son propiedad de
sus respectivos propietarios y/o representantes.
Su utilización es solamente para fines ilustrativos.