|
| 1 | +package com.platzi.functional._17_operators_collectors; |
| 2 | + |
| 3 | +import com.platzi.functional.util.Utils; |
| 4 | + |
| 5 | +import java.util.Collection; |
| 6 | +import java.util.List; |
| 7 | +import java.util.stream.Collectors; |
| 8 | +import java.util.stream.Stream; |
| 9 | + |
| 10 | +public class Operators { |
| 11 | + static void operadores() { |
| 12 | + /* |
| 13 | + Hasta ahora hemos usado lambdas indiscriminadamente en nuestros streams sin revisar o validar |
| 14 | + el estado del stream. Es decir, si ejecutas algunos ejemplos anteriores puede que te encuentres con |
| 15 | + algunas excepciones no explicadas. Este es el tema donde explicaremos que diferencia hay entre usar |
| 16 | +
|
| 17 | + forEach vs map por ejemplo. |
| 18 | + */ |
| 19 | + |
| 20 | + /* |
| 21 | + En nuestros streams, generalmente nos encontramos con elementos y realizamos operaciones sobre ellos. |
| 22 | + */ |
| 23 | + Stream<Integer> numbers = Utils.getListOf(1, 2, 3, 4).stream(); |
| 24 | + numbers.map(i -> i * 2); |
| 25 | + |
| 26 | + /* |
| 27 | + Sin embargo, ciertas operaciones nos retornan un resultado, por ejemplo, map genera un nuevo |
| 28 | + Stream<V> partiendo de un Stream<T>. Es decir, cada que realizamos una operacion `map` en un stream |
| 29 | + lo que hace en realidad es generar un nuevo Stream, donde a cada elemento se le ira aplicando |
| 30 | + la operacion que nosotros definimos. |
| 31 | +
|
| 32 | + Esto puede ser representado: |
| 33 | + */ |
| 34 | + Stream<Integer> numbersV2 = Utils.getListOf(1, 2, 3, 4).stream(); |
| 35 | + Stream<Integer> numbresBy2 = numbersV2.map(i -> i * 2); |
| 36 | + |
| 37 | + /* |
| 38 | + Mas importante aun es entender que una vez aplicado un operador sobre un stream este stream no puede ser |
| 39 | + usado de nuevo: |
| 40 | + */ |
| 41 | + Stream<Integer> squares = numbersV2.map(i -> i * i); //Genera un IllegalStateException. |
| 42 | + //Lo cual representa una pequeña limitante sobre el uso de streams. |
| 43 | + |
| 44 | + /* |
| 45 | + Hay metodos que modifican los datos de nuestro stream y metodos que nos dejan obtener un resultado de |
| 46 | + la iteracion de los elementos del stream. En ambos casos se les conoce como Operations |
| 47 | +
|
| 48 | + Es importante identificar que muchos de estos retornan un nuevo Stream. Tras realizar una operacion |
| 49 | + sobre un stream, el dato que existe en el stream puede cambiar, aqui un ejemplo: |
| 50 | + */ |
| 51 | + Stream<String> courses = Stream.of( |
| 52 | + "Java:Introductorio", |
| 53 | + "Python:Introductorio", |
| 54 | + "Machine Learning:Avanzado", |
| 55 | + "JavaScript:Introductorio", |
| 56 | + "Node.js:Intermedio", |
| 57 | + "Android:Intermedio", |
| 58 | + "iOS:Intermedio" |
| 59 | + ); |
| 60 | + |
| 61 | + Stream<String> introductoryCourses = courses.filter(course -> course.contains("Introductorio")); |
| 62 | + |
| 63 | + Stream<String[]> partsNames = introductoryCourses.map(course -> course.split(":")); |
| 64 | + |
| 65 | + Stream<String[]> partsWithData = partsNames.filter(parts -> parts.length > 1); |
| 66 | + |
| 67 | + Stream<String> justNamesStream = partsWithData.map(courseData -> courseData[0]); |
| 68 | + |
| 69 | + Stream<String> justActualNamesPresent = justNamesStream.filter(name -> !name.isEmpty()); |
| 70 | + |
| 71 | + /* |
| 72 | + O en una version con chaining: |
| 73 | +
|
| 74 | + Stream<String> justNamesStream = courses.filter(c -> c.contains("Introductorio")) |
| 75 | + .map(c -> c.split(":")) |
| 76 | + .filter(parts -> parts.length > 1) |
| 77 | + .map(courseData -> courseData[0]) |
| 78 | + .map(c -> c[0]) |
| 79 | + .filter(name -> !name.isEmpty()); |
| 80 | + */ |
| 81 | + |
| 82 | + /* |
| 83 | + Existen operaciones que nos permite obtener un resultado final despues de operar sobre los elementos de un stream. |
| 84 | +
|
| 85 | + Cuando obtenemos un resultado final de la iteracion de un stream, se dice que se invoco una Operacion Final. |
| 86 | +
|
| 87 | + Es importante entender que aunque tengamos multiples operaciones sobre un Stream, el Stream no sera iterado |
| 88 | + hasta no agregar una operacion final. Esto es benefico pues podemos ir pasando el Stream por diferentes metodos/funciones |
| 89 | + hasta que sea necesario obtener el valor resultante. |
| 90 | + */ |
| 91 | + Stream<List<String>> coursesStream = getCourses(); |
| 92 | + Stream<String> courseDataStream = flatMapCourses(coursesStream); |
| 93 | + Stream<String[]> partsStream = splitInformation(courseDataStream); |
| 94 | + Stream<String[]> filteredPartsStream = filterAdvanceCourses(partsStream); |
| 95 | + Stream<String> advanceCourseNamesStream = getNamesStream(filteredPartsStream); |
| 96 | + |
| 97 | + /* |
| 98 | + hasta este punto, no se ha procesado ningun stream a pesar de las llamadas a metodos. |
| 99 | + Esto es porque no se ha agregado una operacion final que desencadene la iteracion en el |
| 100 | + ningun stream. |
| 101 | +
|
| 102 | + Agregar una operacion final es tan sencillo como agregar una operacion no final: |
| 103 | + */ |
| 104 | + //long totalAdvanceCourses = advanceCourseNamesStream.count(); |
| 105 | + |
| 106 | + /* |
| 107 | + Es hasta este punto que los datos se empiezan a iterar para obtener un resultado. |
| 108 | + Sabemos que `count()` es una operacion final porque no retorna un Stream, retorna un |
| 109 | + resultado de aplicar la operacion correspondiente. |
| 110 | +
|
| 111 | + En muchas ocasiones lo mas comun es encontrarte con una operacion de recoleccion de datos |
| 112 | + utilizando una de las implementaciones de Collector. |
| 113 | +
|
| 114 | + Esta es la manera ideal de convertir un Stream a una coleccion (Set, Map, List, Collection) |
| 115 | + o convertir todos los datos de un Stream a un tipo en concreto (por ejemplo, concatenar todos los elementos) |
| 116 | + */ |
| 117 | + List<String> advanceCourseNamesList = advanceCourseNamesStream.collect(Collectors.toList()); |
| 118 | + |
| 119 | +// |
| 120 | +// |
| 121 | +// |
| 122 | +// |
| 123 | + |
| 124 | + /* |
| 125 | + El ejemplo anterior representa algo sucediendo entre multiples partes de un proyecto, |
| 126 | + una que procese el dato de una peticion web, otro que lo almacene en una base de datos |
| 127 | + otro que haga conversion de datos… etc. |
| 128 | + */ |
| 129 | + } |
| 130 | + |
| 131 | + // |
| 132 | +// |
| 133 | +// |
| 134 | +// |
| 135 | +// |
| 136 | +// |
| 137 | +// |
| 138 | +// |
| 139 | +// |
| 140 | +// |
| 141 | +// |
| 142 | +// |
| 143 | + static Stream<List<String>> getCourses() { |
| 144 | + List<String> nodeCourses = Utils.getListOf("Node.js:Intermedio", "Express.js:Intermedio", "Eventloop:Avanzado"); |
| 145 | + List<String> javaCourses = Utils.getListOf("Spring:Introductorio", "Maven:Intermedio", "Gradle:Avanzado", "Funtional:Introductorio"); |
| 146 | + |
| 147 | + return Stream.of(nodeCourses, javaCourses); |
| 148 | + } |
| 149 | + |
| 150 | + static Stream<String> flatMapCourses(Stream<List<String>> courses) { |
| 151 | + return courses.flatMap(Collection::stream); |
| 152 | + } |
| 153 | + |
| 154 | + static Stream<String[]> splitInformation(Stream<String> coursesData) { |
| 155 | + return coursesData.map(courseData -> courseData.split(":")); |
| 156 | + } |
| 157 | + |
| 158 | + static Stream<String[]> filterAdvanceCourses(Stream<String[]> courses) { |
| 159 | + return courses.filter(data -> data.length > 1) |
| 160 | + .filter(data -> data[1] == "Avanzado"); |
| 161 | + } |
| 162 | + |
| 163 | + static Stream<String> getNamesStream(Stream<String[]> coursesData) { |
| 164 | + return coursesData.map(courseData -> courseData[0]); |
| 165 | + } |
| 166 | +} |
0 commit comments