From 05e89b26411c5d6567f7680eea11ce9c3f188ba4 Mon Sep 17 00:00:00 2001 From: jaimed Date: Fri, 5 Jul 2019 16:48:04 +0200 Subject: [PATCH] Review translations --- es/01-default-static-interface-methods.md | 20 +++--- es/02-lambdas.md | 83 ++++++++++++++++------ es/03-streams.md | 85 ++++++++++++----------- es/04-collectors.md | 41 +++++------ es/05-optionals.md | 46 ++++++------ es/06-map.md | 12 ++-- es/08-date-time-api.md | 66 +++++++++--------- es/12-annotations.md | 2 +- 8 files changed, 201 insertions(+), 154 deletions(-) diff --git a/es/01-default-static-interface-methods.md b/es/01-default-static-interface-methods.md index c490dbb..230b733 100644 --- a/es/01-default-static-interface-methods.md +++ b/es/01-default-static-interface-methods.md @@ -1,11 +1,11 @@ Métodos Estáticos y por defecto en Interfaces -------- -Todos entendemos que deberíamos programar con interfaces. Los interfaces dan al cliente un contrato que deberían usar sin preocuparse en los detalles de la implementación ( p.e. las clases). Por lo tanto, fomentan **[el bajo acoplamiento](https://en.wikipedia.org/wiki/Loose_coupling)**. Diseñar interfaces limpios es uno de los aspectgos más importantos en el diseño de APIs. Uno de los principios SOLID **[Segregación de interfaces](https://en.wikipedia.org/wiki/Interface_segregation_principle)** habla sobre diseñar interfaces específicos para el cliente más pequeños en vez de un interfaz más genérico. El diseño de interfaces es la clave para tener APIs limpios y efectivos para nuestas librerías y aplicaciones. +Todos entendemos que deberíamos programar con interfaces. Los interfaces dan al cliente un contrato que deberían usar sin preocuparse en los detalles de la implementación (p.e. las clases). Por lo tanto, fomentan **[el bajo acoplamiento](https://en.wikipedia.org/wiki/Loose_coupling)**. Diseñar interfaces limpios es uno de los aspectos más importantes en el diseño de APIs. Uno de los principios SOLID **[Segregación de interfaces](https://en.wikipedia.org/wiki/Interface_segregation_principle)** habla sobre diseñar interfaces específicos para el cliente más pequeños en vez de un interfaz más genérico. El diseño de interfaces es la clave para tener APIs limpios y efectivos para nuestas librerías y aplicaciones. > El código de esta sección está en [ch01 package](https://github.com/shekhargulati/java8-the-missing-tutorial/tree/master/code/src/main/java/com/shekhargulati/java8_tutorial/ch01). -Si has diseñado algún API, con el tiempo, habrás sentido la necesidad de añadirle nuevos métodos. Una vez que se publica el API se hace imposible añadir métodos a un interfaz sin romper las implementaciones existentes. Para aclarar este punto vamos a suponer que estamos desarrollando un API sencillo de una calculadora `Calculator` que soporta las operaciones de sumar `add`, restar `subtract`, dividir `divide` y multiplicar `multiply`. Podemos escribir el interfaz `Calculator`como se muestra abajo. ***Para hacerlo sencillo usaremos enteros.*** +Si has diseñado algún API, con el tiempo, habrás sentido la necesidad de añadirle nuevos métodos. Una vez que se publica el API se hace imposible añadir métodos a un interfaz sin romper las implementaciones existentes. Para aclarar este punto vamos a suponer que estamos desarrollando un API sencillo de una calculadora `Calculator` que soporta las operaciones de sumar `add`, restar `subtract`, dividir `divide` y multiplicar `multiply`. Podemos escribir el interfaz `Calculator` como se muestra debajo. ***Para hacerlo sencillo usaremos enteros.*** ```java public interface Calculator { @@ -20,7 +20,7 @@ public interface Calculator { } ``` -Para respaldar este interfaz `Calculator` desarrollaste la implementación de `BasicCalculator` como se muestra abajo. +Para respaldar este interfaz `Calculator` se desarrolló la implementación de `BasicCalculator` como se muestra abajo. ```java public class BasicCalculator implements Calculator { @@ -52,7 +52,7 @@ public class BasicCalculator implements Calculator { ## Métodos de Factoría Estáticos -El API calculadora resultó ser muy útil y fácil de usar. Los usuarios sólo tienen que crear una instancia de `BasicCalculator` y ya pueden usar el API. Comienzas a ver código como el que se muestra abajo. +El API calculadora resultó ser muy útil y fácil de usar. Los usuarios sólo tienen que crear una instancia de `BasicCalculator` y ya pueden usar el API. Basta con usar código como el que se muestra debajo. ```java Calculator calculator = new BasicCalculator(); @@ -72,7 +72,7 @@ class BasicCalculator implements Calculator { } ``` -Luego, escribiremos una clase factoría que nos facilite la instancia de `Calculator` como se muestra abajo. +Luego, escribiremos una clase factoría que nos facilite la instancia de `Calculator` como se muestra debajo. ```java public abstract class CalculatorFactory { @@ -111,7 +111,7 @@ public interface Calculator { ## La Evolución del API con el tiempo -Algunos de los consumidores decidieron o bien, ampliar el API `Calculator` añadiendo métodos como resto `remainder`, o escribit su propia implementación del interfaz `Calculator`. Tras hablar con tus usuarios sacaste la conclusión de que a la mayoría de ellos les gustaría tener un método `remainder` en el interfaz `Calculator`. Parecía un cambio muy simple al API por lo que añadiste un método nuevo. +Algunos de los consumidores decidieron o bien, ampliar el API `Calculator` añadiendo métodos como `remainder`, o escribir su propia implementación del interfaz `Calculator`. Tras hablar con tus usuarios sacaste la conclusión de que a la mayoría de ellos les gustaría tener un método `remainder` en el interfaz `Calculator`. Parecía un cambio muy simple al API por lo que añadiste un método nuevo. ```java public interface Calculator { @@ -156,7 +156,9 @@ default int remainder(int number, int divisor) { ## Herencia múltiple -Una clase puede extender sólo una clase pero puede implementar múltiples interfaces. Ahora que es posible tener implementación de métodos en interfaces Java tiene herencia múltiple de comportamiento. Java ya tenía herencia múltiple a nivel de tipo y ahora tambén a nivel de comportamiento. Existen tres reglas de resolución que ayudan a decidir que método será elegido: +Una clase puede extender sólo una clase pero puede implementar múltiples interfaces. Ahora que es posible tener implementación de métodos en interfaces Java conseguimos herencia múltiple de comportamiento. Java ya tenía herencia múltiple a nivel de tipo con las interfaces y ahora también a nivel de comportamiento. + +Existen tres reglas de resolución que ayudan a decidir que método será elegido: **Regla 1: Los métodos declarados en las clases tendrán preferencia sobre los definidos en las interfaces.** @@ -167,7 +169,7 @@ interface A { } } -class App implements A{ +class App implements A { @Override public void doSth() { @@ -209,7 +211,7 @@ class App implements C, B, A { Esto imprimirá `Dentro de C`. -**Regla 3: Sino, la clase tiene que llamar explicitamente a la implementación que desea** +**Regla 3: Si no, la clase tiene que llamar explícitamente a la implementación que desea** ```java interface A { diff --git a/es/02-lambdas.md b/es/02-lambdas.md index 91f3523..a17067e 100644 --- a/es/02-lambdas.md +++ b/es/02-lambdas.md @@ -1,15 +1,15 @@ Lambdas ----- -Una de las características más importantes de Java 8 es la introducción de las expresiones lambda. Hacen tu código conciso y te permite pasar del comportamiento. Desde hace algún tiempo, se ha criticado a Java por ser verboso y carecer de capacidades de programación funcional. Con la programación funcional volviendose más popular y relevante, se le ha forzado a adoptar el estilo funcional de programación, de otra forma, llegaría a ser irrelevante. +Una de las características más importantes de Java 8 es la introducción de las **expresiones lambda**. Hacen tu código conciso y te permiten pasar del comportamiento. Desde hace algún tiempo, se ha criticado a Java por ser verboso y carecer de capacidades de programación funcional. Con la programación funcional volviéndose más popular y relevante, se le ha forzado a adoptar el estilo funcional de programación; de otra forma llegaría a ser irrelevante. -Java 8 es un gran paso para hacer que el lenguaje más popular del mundo adopte el estilo funcional de programación. Para soportar el estilo de programación funcional, un lenguage necesita soportar funciones como entidades de primer nivel, es decir, poder pasar funciones como parámetros a otras funciones. Antes de Java 8, escribir código de estilo funcional limpio no era posible sin el uso repetitivo de una clase interna (inner class) anónima. Con la introducción de las expresiones lambda, las funciones se han convertido en entidades de primer nivel y se pueden pasar como cualquier otra variable. +Java 8 es un gran paso para hacer que el lenguaje más popular del mundo adopte el estilo funcional de programación. Para soportar el estilo de programación funcional, un lenguage necesita soportar funciones como entidades de primer nivel; es decir, poder pasar funciones como parámetros a otras funciones. Antes de Java 8, escribir código de estilo funcional limpio no era posible sin el uso repetitivo de una clase interna (*inner class*) anónima. Con la introducción de las expresiones lambda, las funciones se han convertido en entidades de primer nivel y se pueden pasar como cualquier otra variable. -Las expresiones lambda te permiten definir una función anónima que no este ligada a un identificador. Puedes usarlas como cualquier otro constructor en tu lenguaje de programación como una declaración de variable. Las expresiones lambda son necesarias si el lenguaje de programación necesita soportar funciones de alto nivel. Las funciones de alto nivel son funciones que, o bien aceptan a otras funciones como argumento, o bien devuelven una función como resultado. +Las expresiones lambda te permiten definir una función anónima que no esté ligada a un identificador. Puedes usarlas como cualquier otra construcción en tu lenguaje de programación, por ejemplo en una declaración de variable. Las expresiones lambda son necesarias si el lenguaje de programación necesita soportar **funciones de alto nivel**. Las funciones de alto nivel son funciones que, o bien aceptan a otras funciones como argumento, o bien devuelven una función como resultado. > El código para esta sección está en el [paquete ch02](https://github.com/shekhargulati/java8-the-missing-tutorial/tree/master/code/src/main/java/com/shekhargulati/java8_tutorial/ch02). -Ahora, con la introducción de las expresiones lambda en Java 8, Java soporta funciones de orden superior. Vamos a ver el ejemplo por excelencia de la expresión Lambda -- una función de ordenación en la clase de Java `Collections`. La función `sort` tiene dos variantes -- una que toma una lista (`List`) como parámetro y la otra que toma una lista (`List`) y un comparador (`Comparator`). La segunda función `sort` es un ejemplo de una función de alto nivel que acepta una expresión lambda como se muestra en el trozo de código de abajo. +Ahora, con la introducción de las expresiones lambda en Java 8, Java soporta funciones de orden superior. Vamos a ver el ejemplo por excelencia de la expresión Lambda: una función de ordenación en la clase de Java `Collections`. La función `sort` tiene dos variantes: una que toma una lista (`List`) como parámetro y la otra que toma una lista (`List`) y un comparador (`Comparator`). La segunda versión de `sort` es un ejemplo de una función de alto nivel que acepta una expresión lambda como se muestra en el trozo de código de debajo. ```java List names = Arrays.asList("shekhar", "rahul", "sameer"); @@ -39,20 +39,20 @@ El cálculo lambda llego a ser la base de fundamentos teóricos fuertes de lengu El concepto principal en el cálculo lambda es la expresión. Una expresión se puede expresar como: ``` - := | | + := | | ``` -* **variable** -- Una variable es un marcador de posición como x, y, z para valores como 1, 2, etc o funciones lambda. +* **variable**: Una variable es un marcador de posición como x, y, z para valores como 1, 2, etc o funciones lambda. -* **función** -- Es una definición de función anónima que toma una variable y produce otra expresión lambda. Por ejemplo, `λx.x*x` es una función para calcular el cuadrado de un número. +* **función**: Es una definición de función anónima que toma una variable y produce otra expresión lambda. Por ejemplo, `λx.x*x` es una función para calcular el cuadrado de un número. -* **aplicación** -- Es el acto de aplicar una función a un argumento. Supón que quieres obtener el cuadrado de 10, en cálculo lambda escribirías una función cuadrado `λx.x*x` y la aplicarías a 10. Esta aplicación de una función daría como resultado en `(λx.x*x) 10 = 10*10 = 100`. No sólo puedes aplicar valores simplea como 10 sino que puedes aplicar una función a otra función para producir otra función. Por ejemplo, `(λx.x*x) (λz.z+10)` producirá una función `λz.(z+10)*(z+10)`. Ahora puedes usar esta función para producir cuadrados de un número más 10. Esto es un ejemplo de funciones de orden superior. +* **aplicación**: Es el acto de aplicar una función a un argumento. Supón que quieres obtener el cuadrado de 10, en cálculo lambda escribirías una función cuadrado `λx.x*x` y la aplicarías a 10. Esta aplicación de una función daría como resultado en `(λx.x*x) 10 = 10*10 = 100`. No sólo puedes aplicar valores simples como 10 sino que puedes aplicar una función a otra función para producir otra función. Por ejemplo, `(λx.x*x) (λz.z+10)` producirá una función `λz.(z+10)*(z+10)`. Ahora puedes usar esta función para producir cuadrados de un número más 10. Esto es un ejemplo de funciones de orden superior. Ahora que entiendes el cálculo lambda y su impacto en los lenguajes de programación funcional vamos a aprender como se implementan en Java 8. ## Pasar el comportamiento antes de Java 8 -Antes de Java 8, la única forma de pasar el comportamiento era usando clases anónimas. Supón que quieres enviar un email en otro hilo después del registro de un usuario. Antes de Java 8 esribirías código como el que se muestra abajo. +Antes de Java 8, la única forma de pasar el comportamiento era usando clases anónimas. Supón que quieres enviar un email en otro hilo después del registro de un usuario. Antes de Java 8 escribirías código como el que se muestra abajo. ```java sendEmail(new Runnable() { @@ -69,7 +69,7 @@ El método sendEmail tiene la siguiente firma. public static void sendEmail(Runnable runnable) ``` -El problema con el código de arriba no es sólo que tenemos que encapsular nuestra acción p. ej. el método `run` en un objeto, sino que el mayor problema es que pierde la intención del programador, p. ej. pasar el comportamiento a la función `sendEmail`. Si has usado bibliotecas como Guava, habrás sufrido de verdad el dolor de escribir clases anónimas. A continuación se muestra un sencillo ejemplo de como filtrar todas las tareas con **lambda** en su título. +El problema con el código de arriba no es sólo que tenemos que encapsular nuestra acción (p.ej. el método `run` en un objeto) sino que el mayor problema es que pierde la intención del programador; p. ej. pasar el comportamiento a la función `sendEmail`. Si has usado bibliotecas como Guava, habrás sufrido de verdad el dolor de escribir clases anónimas. A continuación se muestra un sencillo ejemplo de como filtrar todas las tareas con **lambda** en su título. ```java Iterable lambdaTasks = Iterables.filter(tasks, new Predicate() { @@ -97,7 +97,7 @@ Vamos a observar de nuevo el ejemplo Collections.sort para poder entender como f Comparator comparator = (first, second) -> first.length() - second.length(); ``` -La expresión lambda que escribimos se correspondía al método `compare` del interfaz Comparator. La firma de la función `compare`se muestra a continuación. +La expresión lambda que escribimos se correspondía al método `compare` del interfaz Comparator. La firma de la función `compare` se muestra a continuación. ```java int compare(T o1, T o2); @@ -105,7 +105,7 @@ int compare(T o1, T o2); `T` es el tipo de parámetro pasado al interfaz `Comparator`. En este caso será un `String` ya que estamos trabajando con una lista de `String` p. ej. names. -En la expresión lambda no tuvimos que poner el tipo explicitamente -- String, el compilador `javac` lo dedujo del contexto. El compilador de Java dedujo que ambos parámetros deberían ser String ya que estamos ordenando una lista de String y el método `compare` sólo usa un tipo T. El acto de deducir el tipo del contexto se llama **Type Inference** (inferencia de tipo). Java 8 mejora el sistema de inferencia de tipos ya existente y lo hace más robusto y potente para soportar expresiones lambda. El compilador `Javac` busca internamente la información cerca de tu expresión lambda y la usa para encontrar el tipo correcto de los parámetros. +En la expresión lambda no tuvimos que poner el tipo explicitamente (String); el compilador `javac` lo dedujo del contexto. El compilador de Java dedujo que ambos parámetros deberían ser String ya que estamos ordenando una lista de String y el método `compare` sólo usa un tipo T. El acto de deducir el tipo del contexto se llama **Type Inference** (inferencia de tipo). Java 8 mejora el sistema de inferencia de tipos ya existente y lo hace más robusto y potente para soportar expresiones lambda. El compilador `Javac` busca internamente la información cerca de tu expresión lambda y la usa para encontrar el tipo correcto de los parámetros. > En la mayoría de los casos, `javac` deducirá el tipo del contexto. En caso de que no pueda resolver el tipo debido a una perdida de contexto o a un contexto incompleto el código no compilará. Por ejemplo, si quitamos la información del tipo `String` de `Comparator`, el código fallará al compilar como se muestra a continuación. @@ -115,7 +115,7 @@ Comparator comparator = (first, second) -> first.length() - second.length(); // ## ¿Cómo funcionan las expresiones lambda en Java 8? -Puedes haber notado que el tipo de la expresión lambda es algún interfaz, como `Comparator` en el ejemplo anterior. No puedes usar cualquier interfaz con una expresión lambda. ***Sólo aquellos interfaces con un único método abstracto se pueden usar con expresiones lambda***. Estas clases de interfaces se llaman **Interfaces funcionales** y se pueden anotar con la anotación `@FunctionalInterface`. El interfaz `Runnable` es un ejemplo de intefaz funcional como se muestra abajo. +Puedes haber notado que el tipo de la expresión lambda es algún interfaz, como `Comparator` en el ejemplo anterior. No puedes usar cualquier interfaz con una expresión lambda, ***sólo aquellas interfaces con un único método abstracto se pueden usar con expresiones lambda***. Estas clases de interfaces se llaman **Interfaces funcionales** y se pueden anotar con la anotación `@FunctionalInterface`. El interfaz `Runnable` es un ejemplo de interfaz funcional como se muestra debajo. ```java @FunctionalInterface @@ -126,9 +126,9 @@ public interface Runnable { La anotación `@FuncionalInterface` no es obligatoria pero puede ayudar a las herramientas a saber que un interfaz es un interfaz funcional y realizar acciones significativas. Por ejemplo, si tratas de compilar un interfaz anotado con `@FunctionalInterface` y tiene varios métodos abstractos la compilación fallará con un error ***Se han encontrado varios métodos abstractos no anulables***. Igualmente, si añades la anotación `@FunctionaInterface` a una interfaz sin ningún método, p. ej. un interfaz marcador, obtendrás el mensaje de error ***No se encuentra un método objetivo***. -Vamos a responder a una de las preguntas más importantes que te pueden venir a la mente, ***¿Son las expresiones lambda simplemente el azucar sintáctico sobre las clases internas anónimas o cómo se traducen a bytecode los interfaces funcionales?***. +Vamos a responder una de las preguntas más importantes que te pueden venir a la mente: **Son las expresiones lambda simplemente el azúcar sintáctico sobre las clases internas anónimas o cómo se traducen a bytecode los interfaces funcionales?**. -La respuesta corta es **NO**, Java 8 no usa las clases internas anónimas principalmente por dos motivos: +La respuesta corta es **NO**; Java 8 no usa las clases internas anónimas principalmente por dos motivos: 1. **Impacto en el rendimiento**: Si las expresiones lambda fuesen implementadas mediante clases anónimas entonces tendríamos un fichero en disco por cada expresión lambda. Como estas clases se cargan en el arranque, el tiempo de arranque de la JVM se incrementaría, ya que todas las clases necesitan ser cargadas y verificadas antes de poder usarlas. @@ -136,7 +136,7 @@ La respuesta corta es **NO**, Java 8 no usa las clases internas anónimas princi ### El uso de invokedynamic -Los diseñadores de Java 8 tomaron la decisión de usar la instrucción añadida en Java 7 `invokedynamic` para posponer la estrategia de traducción en tiempo de ejecución. Cuando `javac` compila el código, captura la expresión lambda y genera un lugar de llamada `invokedynamic` (llamado factoría lambda). Cuando se invoca el lugar de llamada `invokedynamic`, devuelve una instancia del interfaz funcional al que se convierte el lambda. Por ejemplo, si observamos el código byte de nuestro ejemplo Collections.sort, se mostrará como aparece abajo. +Los diseñadores de Java 8 tomaron la decisión de usar la instrucción añadida en Java 7 `invokedynamic` para posponer la estrategia de traducción en tiempo de ejecución. Cuando `javac` compila el código, captura la expresión lambda y genera un lugar de llamada `invokedynamic` (llamado **factoría lambda**). Cuando se invoca el lugar de llamada `invokedynamic`, devuelve una instancia del interfaz funcional al que se convierte el lambda. Por ejemplo, si observamos el código byte de nuestro ejemplo Collections.sort, se mostrará como aparece abajo. ``` public static void main(java.lang.String[]); @@ -169,7 +169,7 @@ public static void main(java.lang.String[]); } ``` -La parte interesante del código byte mostrado arriba está en la línea 23 `23: invokedynamic #7, 0 // InvokeDynamic #0:compare:()Ljava/util/Comparator;` donde se hace una llamada a `invokedynamic`. +La parte interesante del código byte mostrado arriba está en la línea 23, donde se hace una llamada a `invokedynamic`. El segundo paso es convertir el cuerpo de la expresión lambda en un método que se invocará a través de la instrucción `invokeDynamic`. Este es el paso donde los que implementan la JVM tienen la libertad de elegir su propia estrategia. @@ -226,17 +226,51 @@ Veremos más interfaces funcionales a lo largo del tutorial. ## Referencias de métodos -Habrá veces que crearás expresiones lambda que sólo llames a un método específico como `Function strToLength = str -> str.length();`.El lambda sólo llama al método `length()` del objeto `String`. Esto se podría simplificar usando referencias de métodos com `Function strToLength = String::length;`. Se puede ver como una notación abreviada de la expresión abreviada que sólo llama a un método. En la expresión `String::length`, `String` es la referencia destino, `::` es el delimitador y `length` es la función que se invocará en la referencia destino. Puedes usar referencias de métodos tanto en métodos estáticos como en métodos de instancias. +Habrá veces que crearás expresiones lambda que sólo llames a un método específico como + +```java +Function strToLength = str -> str.length(); +``` + +El lambda sólo llama al método `length()` del objeto `String`. Esto se podría simplificar usando referencias de métodos como + +```java +Function strToLength = String::length; +``` + +Se puede ver como una notación abreviada de la expresión abreviada que sólo llama a un método. En la expresión `String::length`, `String` es la referencia destino, `::` es el delimitador y `length` es la función que se invocará en la referencia destino. Puedes usar referencias de métodos tanto en métodos estáticos como en métodos de instancias. ### Referencias de métodos estáticos -Supón que tenemos que encontrar el número máximo de una lista de números, podemos escribir una referencia de método `Function, Integer> maxFn = Collections::max`. `max` es un método estático de la clase `Collections` que toma un argumento de tipo `List`. Puedes invocarlo de la siguiente forma `maxFn.apply(Arrays.asList(1, 10, 3, 5))`. La expresión lambda de arriba es equivalente a esta otra `Function, Integer> maxFn = (numbers) -> Collections.max(numbers);` +Supón que tenemos que encontrar el número máximo de una lista de números, podemos escribir una referencia de método + +```java +Function, Integer> maxFn = Collections::max +``` + +`max` es un método estático de la clase `Collections` que toma un argumento de tipo `List`. Puedes invocarlo de la siguiente forma: + +```java +maxFn.apply(Arrays.asList(1, 10, 3, 5)) +``` + +La expresión lambda de arriba es equivalente a esta otra: + +```java +Function, Integer> maxFn = (numbers) -> Collections.max(numbers); +``` ### Referencias de métodos de instancia -Se usa en referencias de métodos a un método de instancia, por ejemplo `String::toUpperCase` llama al método `toUpperCase` en una referencia a `String`. También puedes usar un método de referencia con parámetros, por ejemplo `BiFunction concatFn = String::concat`. `concatFn` se puede invocar con `concatFn.apply("shekhar", "gulati")`. El método `concat` de `String` se invoca sobre un objeto `String` y se pasa como parámetro `"shekhar".concat("gulati")`. +Se usa en referencias de métodos a un método de instancia, por ejemplo `String::toUpperCase` llama al método `toUpperCase` en una referencia a `String`. También puedes usar un método de referencia con parámetros, por ejemplo -## Ejercicio >> Lambdificame +```java +BiFunction concatFn = String::concat +``` + +`concatFn` se puede invocar con `concatFn.apply("shekhar", "gulati")`. El método `concat` de `String` se invoca sobre un objeto `String` y se pasa como parámetro `"shekhar".concat("gulati")`. + +## Ejercicio >> Lambdifícame! Vamos a ver el código que se muestra a continuación y aplicar lo que hemos aprendido hasta ahora. @@ -264,9 +298,9 @@ public class Exercise_Lambdas { } ``` -El código anterior primero recupera todas las tareas, `Tasks`, con el método `GetTasks`. No nos interesa la implementación de `getTasks`, este método nos devolvería las tareas accediento a un servicio web, a una base de datos o a la memoria. Una vez que tenemos las tareas, las filtraremos y extraeremos el campo título, `title` de cada una. Añadiremos el título extraído a una lista para, finalmente, devolver todos los titulos leídos. +El código anterior primero recupera todas las tareas, `tasks`, con el método `getTasks`. No nos interesa la implementación de `getTasks`; este método nos devolvería las tareas accediendo a un servicio web, a una base de datos o a la memoria. Una vez que tenemos las tareas, las filtraremos y extraeremos el campo título, `title` de cada una. Añadiremos el título extraído a una lista para, finalmente, devolver todos los titulos leídos. -Vamos a empreza con la refactorización más simple -- usar `forEach` en una lista con una referencia de método. +Vamos a empezar con la refactorización más simple: usar `forEach` en una lista con una referencia de método. ```java public class Exercise_Lambdas { @@ -342,10 +376,13 @@ Usar una referencia de método para el extractor. ```java public static void main(String[] args) { List tasks = getTasks(); + List titles = filterAndExtract(tasks, task -> task.getType() == TaskType.READING, Task::getTitle); titles.forEach(System.out::println); + List createdOnDates = filterAndExtract(tasks, task -> task.getType() == TaskType.READING, Task::getCreatedOn); createdOnDates.forEach(System.out::println); + List filteredTasks = filterAndExtract(tasks, task -> task.getType() == TaskType.READING, Function.identity()); filteredTasks.forEach(System.out::println); } diff --git a/es/03-streams.md b/es/03-streams.md index b0d4063..d9236a5 100644 --- a/es/03-streams.md +++ b/es/03-streams.md @@ -1,9 +1,9 @@ Flujos ------ -En el [capítulo 2](./02-lambdas.md), aprendimos como los lambdas pueden ayudarnos a escribir código más limpio y conciso permitiéndonos pasar comportamiento sin la necesidad de crear una clase. Los lambdas son construcciones del lenguaje muy simples que ayudan al desarrollador a expresar su intentión al vuelo usando interfaces funcionales. El poder real de los lambdas se puede experimentar cuando se diseña un API pensando en ellos p. ej. un API fluído que hace uso de interfaces funcionales (lo vimos en el [capítulo de los lambdas](./02-lambdas.md#¿Necesito escribir mis propios interfaces funcionales?)). +En el [capítulo 2](./02-lambdas.md), aprendimos como los lambdas pueden ayudarnos a escribir código más limpio y conciso permitiéndonos pasar comportamiento sin la necesidad de crear una clase. Los lambdas son construcciones del lenguaje muy simples que ayudan al desarrollador a expresar su intención al vuelo usando interfaces funcionales. El poder real de los lambdas se puede experimentar cuando se diseña un API pensando en ellos; p. ej. un API fluído que hace uso de interfaces funcionales (lo vimos en el [capítulo de los lambdas](./02-lambdas.md#): ¿necesito escribir mis propios interfaces funcionales?). -Una de estas APIs, que hace un uso intensivo de los lambdas, es el API de flujos (`Stream`) introducido en el JDK 8. Los flujos proporcionan un alto nivel de abstracción para expresar cálculos en las colecciones de Java de una manera declarativa similar a como SQL te ayuda en las consultas a la base de datos. Declarativa quiere decir que los desarrolladores escriben lo que quieren hacer en vez de como se haría. En este capítulo hablaremos del porque de la necesidad de un nuevo API de procesamiento de datos, de la diferencia entre una colección (`Collection`) y un flujo (`Stream`) y de como usar el API de flujos en tus aplicaciones. +Una de estas APIs, que hace un uso intensivo de los lambdas, es el API de **flujos** (`Stream`) introducido en el JDK 8. Los flujos proporcionan un alto nivel de abstracción para expresar cálculos en las colecciones de Java de una manera declarativa similar a como SQL te ayuda en las consultas a la base de datos. Declarativa quiere decir que los desarrolladores escriben lo que quieren hacer en vez de como se haría. En este capítulo hablaremos del porque de la necesidad de un nuevo API de procesamiento de datos, de la diferencia entre una colección (`Collection`) y un flujo (`Stream`) y de como usar el API de flujos en tus aplicaciones. > El código de esta sección está en el [paquete ch03](https://github.com/shekhargulati/java8-the-missing-tutorial/tree/master/code/src/main/java/com/shekhargulati/java8_tutorial/ch03). @@ -13,11 +13,11 @@ En mi opinión, existen dos razones: 1. El API de colecciones (`Collection`) no proporciona construcciones de un mayor nivel para consultar los datos por lo que los desarrolladores se ven forzados a escribir un montón de código repetido para la mayoría de las tareas triviales. -2. Tiene un soporte del lenguaje limitado para procesar datos de la clase `Collection` en paralelo. Deja en manos del desarrollador el uso de construcciones concurrentes del lenguaje Java y el proceso eficaz y eficiente de datos en paralelo. +2. Existe un soporte del lenguaje limitado para procesar datos de la clase `Collection` en paralelo. Deja en manos del desarrollador el uso de construcciones concurrentes del lenguaje Java y el proceso eficaz y eficiente de datos en paralelo. ## El proceso de datos antes de Java 8 -Observa el código que se muestra a continuación y trata de predecir que hace. +Observa el código que se muestra a continuación y trata de predecir qué hace. ```java public class Example1_Java7 { @@ -44,7 +44,7 @@ public class Example1_Java7 { } ``` -El código mostrado imprime todos los títulos de tareas de lectura ordenados por su longitud. Todos los programadores de Java escriben este tipo de código todos los días. Para escribir ese programa tan simple tuvimos que escribir 15 líneas de código Java. El mayor problema de ese código no es el número de líneas que un programador tiene que escribir sino que pierde la intención del programador. P. Ej. filtrar las tareas de lectura, ordenar por la longitud del título y transformarlas a una lista de cadenas. +El código mostrado imprime todos los títulos de tareas de lectura ordenados por su longitud. Todos los programadores de Java escriben este tipo de código todos los días. Para escribir este programa tan simple tuvimos que escribir 15 líneas de código Java. El mayor problema de este código no es el número de líneas que un programador tiene que escribir sino que pierde la intención del programador. P. Ej. filtrar las tareas de lectura, ordenar por la longitud del título y transformarlas a una lista de cadenas. ## El proceso de datos en Java 8 @@ -69,9 +69,9 @@ public class Example1_Stream { El código mostrado construye una tubería compuesta de múltiples operaciones de flujo que desglosamos ahora. -* **stream()** - Creaste una tubería de flujo invocando el método `stream()` en la colección origen. P. ej. `tasks` `List`. +* **stream()** - Crea una tubería de flujo invocando el método `stream()` en la colección origen. P. ej. `List` `tasks`. -* **filter(Predicate)** - Esta operación extrae los elementos del flujo que cumplan la condición definida en el predicado. Una vez que tienes un flujo puedes invocar sobre él cero o más operaciones intermedias. La expresión lambda `task -> task.getType() == TaskType.READING` define un predicado para filtrar todas las tareas de lectura. El tipo de la expresión lambda es `java.util. function.Predicate`. +* **filter(Predicate)** - Esta operación extrae los elementos del flujo que cumplan la condición definida en el predicado. Una vez que tienes un flujo puedes invocar sobre él cero o más operaciones intermedias. La expresión lambda `task -> task.getType() == TaskType.READING` define un predicado para filtrar todas las tareas de lectura. El tipo de la expresión lambda es `java.util.function.Predicate`. * **sorted(Comparator)**: Esta operación devuelve un flujo con todos los elementos ordenados por el comparador definido por la expresión lambda. P. ej. la expresión `(t1, t2) -> t1.getTitle().length() - t2.getTitle().length()` del ejemplo anterior. @@ -92,11 +92,13 @@ En mi opinión, el código de Java 8 es mucho mejor por los siguientes motivos: 4. No se require duplicar código para expresar el proceso de datos. Los desarrolladores ya no tienen que escribir bucles `for` o crear colecciones temporales para almacenar los datos, todo lo proporciona el propio API. -5. Los flujos no modifican la colección original, +5. Los flujos no modifican la colección original. ## ¿Qué es un flujo (`Stream`)? -Un flujo es una vista abstracta de algunos datos. Por ejemplo, un flujo puede ser una vista de una lista, de líneas de un fichero o de cualquier secuencia de elementos. El API de flujo proporciona operaciones de agragación que pueden ejecutarse secuencialmente o en paralelo. ***Una cosa que los desarrolladores deberían de tener en cuenta es que `Stream` es un nivel mucho más alto de abstracción no una estructura de datos. `Stream` no guarda tus datos.*** Los flujos son **vagos** por naturaleza y sólo se calculan cuando se accede a ellos. Esto nos permite producir flujos infinitos de datos. En Java 8 puedes escribir de manera muy sencilla un flujo que produzca identificadores únicos infinitos como se muestra a continuación. +Un flujo es una vista abstracta de algunos datos. Por ejemplo, un flujo puede ser una vista de una lista, de líneas de un fichero o de cualquier secuencia de elementos. El API de flujo proporciona operaciones de agregación que pueden ejecutarse secuencialmente o en paralelo. ***Una cosa que los desarrolladores deberían de tener en cuenta es que `Stream` es un nivel mucho más alto de abstracción no una estructura de datos. `Stream` no guarda tus datos.*** Los flujos son **vagos** por naturaleza y sólo se calculan cuando se accede a ellos. Esto nos permite producir flujos infinitos de datos. + +En Java 8 puedes escribir de manera muy sencilla un flujo que produzca identificadores únicos infinitos como se muestra a continuación. ``` public static void main(String[] args) { @@ -104,7 +106,7 @@ public static void main(String[] args) { } ``` -Existen varios métodos estáticos de factoría como `of`, `generate` e `iterate` en el interfaz de `Stream` que se pueden usar para crear instancias de `Stream`. El método `generate` mostrado arriba usa un proveedor (`Supplier`). Un `Supplier` es un interfaz funcional que describe una función que no tiene entrada y que produce un valor. Pasamos al método `generate` un proveedor que genera un identificador único cuando se invoca. +Existen varios métodos estáticos de factoría como `of`, `generate` e `iterate` en el interfaz de `Stream` que se pueden usar para crear instancias de `Stream`. El método `generate` mostrado arriba usa un **proveedor** (`Supplier`). Un `Supplier` es un interfaz funcional que describe una función que no tiene entrada y que produce un valor. Pasamos al método `generate` un proveedor que genera un identificador único cuando se invoca. ```java Supplier uuids = () -> UUID.randomUUID().toString() @@ -119,7 +121,7 @@ public static void main(String[] args) { } ``` -Java 8 permite crear flujos a partir de una colección invocando el método `stream`. El flujo soporta operaciones de proceso de datos de manera que los desarrolladores puedan expresar cálculos usando construcciones de proceso de datos de más alto nivel. +Java 8 permite crear flujos a partir de una colección invocando el método `stream()`. El flujo soporta operaciones de proceso de datos de manera que los desarrolladores puedan expresar cálculos usando construcciones de proceso de datos de más alto nivel. ## Collection vs Stream @@ -135,17 +137,20 @@ La tabla que mostramos a continuación explica las diferencias entre `Collection ![Collection vs Stream](https://whyjava.files.wordpress.com/2015/10/collection_vs_stream.png) -Vamos a ver en detalle la iteración externa contra la iteración interna y la evaluación perezosa. +Vamos a comparar en detalle la iteración externa con la iteración interna y la evaluación perezosa. ### Iteración externa contra iteración interna -La diferencia entre el código del API Stream de Java 8 y el código del API Collection mostrado anteriormente es quien controla la iteración, el iterador o el cliente que usa el iterador. Los usuarios del API Stream sólo proporcionan las operaciones que quieren usar y el iterador aplica estas operaciones a cada elemento de la colección subyacente. Cuando la iteración sobre la colección subyacente la maneja el propio iterador se denomina **iteración interna**. Por otro lado, cuando la iteración la maneja el cliente de denomina **iteración externa**. El uso de `for-each` en el código del API de Collection es un ejemplo de **iteración externa**. +La diferencia entre el código del API Stream de Java 8 y el código del API Collection mostrado anteriormente es **quién** controla la iteración; el iterador o el cliente que usa el iterador. Los usuarios del API Stream sólo proporcionan las operaciones que quieren usar y el iterador aplica estas operaciones a cada elemento de la colección subyacente. Cuando la iteración sobre la colección subyacente la maneja el propio iterador se denomina **iteración interna**. Por otro lado, cuando la iteración la maneja el cliente de denomina **iteración externa**. El uso de `for-each` en el código del API de Collection es un ejemplo de **iteración externa**. -Algunos podrían argumentar que en el código del API de Collection no tuvimos que usar el iterador, ya que el `for-each`, ya se preocupo de eso, pero el `for-each` no es más que un azúcar sintáctico de la iteración manual usando el API iterador. El `for-each`, aunque es muy sencillo, tiene ciertas desventajas -- 1) Es inherentemente secuencial 2) Conduce a código imperativo 3) Es complicado de paralelizar. +Algunos podrían argumentar que en el código del API de Collection no tuvimos que usar el iterador, ya que el `for-each` ya se preocupó de eso, pero el `for-each` no es más que un *azúcar* sintáctico de la iteración manual usando el API iterador. El `for-each`, aunque es muy sencillo, tiene ciertas desventajas: 1) Es inherentemente secuencial 2) Conduce a código imperativo 3) Es complicado de paralelizar. ### Evaluación perezosa -Los flujos no se evalúan hasta que no se invoca una operación final sobre ellos. La mayoría de las operaciones del API de Stram devuelven un `Stream`. Estas operaciones no realizan ninguna ejecución sólo construyen la tubería. Vamos a observar el código siguiente y tratar de predecir su salida. +Los flujos no se evalúan hasta que no se invoca una operación final sobre ellos. La mayoría de las operaciones del API de Stre + + +am devuelven un `Stream`. Estas operaciones no realizan ninguna ejecución sólo construyen la tubería. Vamos a observar el código siguiente y tratar de predecir su salida. ```java List numbers = Arrays.asList(1, 2, 3, 4, 5); @@ -176,19 +181,19 @@ Exception in thread "main" java.lang.ArithmeticException: / by zero ## Usando el API Stream -El API Stream proporciona un montón de operaciones que los desarrolladores pueden usar para consultar datos en colecciones. Las operaciones de Stream pueden ser de dos tipos -- operación intermedia u operación final. +El API Stream proporciona un montón de operaciones que los desarrolladores pueden usar para consultar datos en colecciones. Las operaciones de Stream pueden ser de dos tipos: operación intermedia u operación final. **Operaciones intermedias** son funciones que producen otros flujos a partir de uno existente como `filter`, `map`, `sorted`, etc. -**Operaciones terminales** son funciones que no generan un flujo como resultado `collect(toList())`, `forEach`, `count`, etc. +**Operaciones terminales** son funciones que no generan un flujo como resultado: `collect(toList())`, `forEach`, `count`, etc. -Las operaciones intermedias te permiten construir la tubería que se ejecutará cuando llames a una operación final. Aquí se muestran la list de funciones que son parte del API Stream. +Las operaciones intermedias te permiten construir la tubería que se ejecutará cuando llames a una operación final. Aquí se muestra la lista de funciones que son parte del API Stream. stream-api ### Dominio de ejemplo -A través de este tutorial usaremos el dominio de gestión de tarea para explicar los conceptos. Nuestro dominio de ejemplo tiene una clase llamada Task (Tarea) -- una tarea a ser realizada por el usuario. La clase es como se muestra a continuación. +A través de este tutorial usaremos el dominio de gestión de tarea para explicar los conceptos. Nuestro dominio de ejemplo tiene una clase llamada Task (Tarea): una tarea a ser realizada por el usuario. La clase es como se muestra a continuación. ```java import java.time.LocalDate; @@ -223,7 +228,7 @@ Task task5 = new Task("Read Domain Driven Design book", TaskType.READING, LocalD List tasks = Arrays.asList(task1, task2, task3, task4, task5); ``` -> No trataremos el API de fecha y hora de Java 8 en este capítulo. Por ahora sólo tenlo en cuenta como el API fluído para trabajar con fechas. +> No trataremos el API de fecha y hora de Java 8 en este capítulo. Por ahora sólo tenlo en cuenta como un API fluído para trabajar con fechas. ### Ejemplo 1: Encuentra todas los títulos de tareas de lectura ordenadas por la fecha de creación @@ -234,7 +239,7 @@ El primer ejemplo que trataremos será encontrar todos los títulos de tareas de 3. Obtener el título de cada tarea. 4. Recoger los títulos devueltos en una lista. -Las cuatro operaciones se pueden desarrollar de forma sencilla de la forma siguiente. +Las cuatro operaciones se pueden desarrollar de forma sencilla de la siguiente forma. ```java private static List allReadingTasks(List tasks) { @@ -288,7 +293,7 @@ public List allReadingTasksSortedByCreatedOnDesc(List tasks) { ### Ejemplo 2: Encuentra tareas distintas -Supón que tenemos un conjunto de datos que contiene tareas duplicadas. Podemos eliminar fácilmente los duplicados y quedarnos con los elementos diferentes usando el método `distinct` en el flujo. +Supón que tenemos un conjunto de datos que contiene tareas duplicadas. Podemos eliminar fácilmente los duplicados y quedarnos con los elementos diferentes usando el método `distinct()` en el flujo. ```java public List allDistinctTasks(List tasks) { @@ -300,7 +305,7 @@ El método `distinct()` convierte un flujo en otro sin duplicados. Usa el métod ### Ejemplo 3: Encuentra la 5 primeras tareas de lectura ordenadas por fecha de creación -La función `limit` se puede usar para limitar el conjunto de resultados a un tamaño dado. `limit` es una operación de circuito corto lo que significa que no evalúa todos los elementos para encontrar el resultado. +La función `limit()` se puede usar para limitar el conjunto de resultados a un tamaño dado. `limit()` es una operación de circuito corto lo que significa que no evalúa todos los elementos para encontrar el resultado. ```java public List topN(List tasks, int n){ @@ -313,7 +318,7 @@ public List topN(List tasks, int n){ } ``` -Puedes usar el método `limit` junto a `skip` como se muestra a continuación para crear paginación. +Puedes usar el método `limit()` junto a `skip()` como se muestra a continuación para crear paginación. ```java // La página comienza en 0. Así que la segunda página (`page`) será 1 y la página n será n-1. @@ -328,7 +333,7 @@ List readingTaskTitles = tasks.stream(). ### Ejemplo 4: Cuenta todas las tareas de lectura -Para obtener el total de tareas de lecturas, podemos usar el método `count` del flujo. Esta operación es final. +Para obtener el total de tareas de lecturas, podemos usar el método `count()` del flujo. Esta operación es final. ```java public long countAllReadingTasks(List tasks) { @@ -347,7 +352,7 @@ Para encontrar todas las etiquetas distintias tenemos que realizar las siguiente 3. Eliminar las duplicadas. 4. Y finalmente recoger el resultado en una lista. -La primera y la segunda operación se pueden realizar usando la operación `flatMap` en el flujo de `tasks`. La operación `flatMap` agrupa los flujos devueltos por cada llamada a `tasks.getTags().stream()` en un único flujo. Una vez que tenemos todas las etiquetas en un sólo flujo, basta con usar el método `distinct` para obtener todas las etiquetas distintas. +La primera y la segunda operación se pueden realizar usando la operación `flatMap()` en el flujo de `tasks`. La operación `flatMap()` agrupa los flujos devueltos por cada llamada a `tasks.getTags().stream()` en un único flujo. Una vez que tenemos todas las etiquetas en un sólo flujo, basta con usar el método `distinct()` para obtener todas las etiquetas distintas. ```java private static List allDistinctTags(List tasks) { @@ -357,7 +362,7 @@ private static List allDistinctTags(List tasks) { ### Ejemplo 6: Comprueba si todas las tareas de lectura tienen la etiqueta `books` -El API Stream tiene métodos que permiten al usuario comprobar si elementos del conjunto de datos contienen una propiedad dada. Estos métodos son `allMatch`, `anyMatch`, `noneMatch`, `findFirst` y `findAny`. Para comprobar si todos los títulos leídos tienen una etiqueta con el nombre `books` podemos escribir lo siguiente. +El API Stream tiene métodos que permiten al usuario comprobar si elementos del conjunto de datos contienen una propiedad dada. Estos métodos son `allMatch()`, `anyMatch()`, `noneMatch()`, `findFirst()` y `findAny()`. Para comprobar si todos los títulos leídos tienen una etiqueta con el nombre `books` podemos escribir lo siguiente. ```java public boolean isAllReadingTasksWithTagBooks(List tasks) { @@ -367,7 +372,7 @@ public boolean isAllReadingTasksWithTagBooks(List tasks) { } ``` -Para comprobar si alguna tarea de lectura tiene una etiqueta `java8` se puede usar la operación `anyMatch` como se muestra a continuación. +Para comprobar si alguna tarea de lectura tiene una etiqueta `java8` se puede usar la operación `anyMatch()` como se muestra a continuación. ```java public boolean isAnyReadingTasksWithTagJava8(List tasks) { @@ -379,7 +384,7 @@ public boolean isAnyReadingTasksWithTagJava8(List tasks) { ### Ejemplo 7: Crear un resumen de todos los títulos -Supón que quieres crear un resumen de todos los títulos, para eso puedes usar la operación `reduce` que reduce el flujo a un valor. La función `reduce` toma un lambda que une los elementos del flujo. +Supón que quieres crear un resumen de todos los títulos. Para eso puedes usar la operación `reduce()` que reduce el flujo a un valor. La función `reduce()` toma un lambda que une los elementos del flujo. ```java public String joinAllTaskTitles(List tasks) { @@ -392,33 +397,33 @@ public String joinAllTaskTitles(List tasks) { ### Ejemplo 8: Trabajando con flujos de elementales -A parte del flujo genérico que trabaja con objetos, Java 8 también proporciona flujos específicos que trabajan con tipos elementales como int, long y double. Vamos a ver unos pocos ejemplos con flujos de elementales. +Aparte del flujo genérico que trabaja con objetos, Java 8 también proporciona **flujos específicos** que trabajan con tipos elementales como *int*, *long* y *double*. Vamos a ver unos pocos ejemplos con flujos de elementales. -Para crear un rango de valores podemos usar el método `range` que crea un flujo con valores comenzando en el 0 y terminando en el 9. Excluye el 10. +Para crear un rango de valores podemos usar el método `range()` que crea un flujo con valores comenzando en el 0 y terminando en el 9. Excluye el 10. ```java IntStream.range(0, 10).forEach(System.out::println); ``` -El método `rangeClosed` te permite crear flujos que también incluyen el límite superior. Así que el flujo de antes comenzaría en 1 y terminaría en 10. +El método `rangeClosed()` te permite crear flujos que también incluyen el límite superior. Así que el flujo de antes comenzaría en 1 y terminaría en 10. ```java IntStream.rangeClosed(1, 10).forEach(System.out::println); ``` -Puedes crear flujos infinitos usando el método `iterate` en flujos de elementales. +Puedes crear flujos infinitos usando el método `iterate()` en flujos de elementales. ```java LongStream infiniteStream = LongStream.iterate(1, el -> el + 1); ``` -Para filtrar todos los números pares en un flujo infinito se puede escribir lo siguient +Para filtrar todos los números pares en un flujo infinito se puede escribir lo siguiente: ```java infiniteStream.filter(el -> el % 2 == 0).forEach(System.out::println); ``` -Podemos limitar el flujo resultado usando la operación `limit`. +Podemos limitar el flujo resultado usando la operación `limit()`. ```java infiniteStream.filter(el -> el % 2 == 0).limit(100).forEach(System.out::println); @@ -441,7 +446,7 @@ Arrays.stream(tags, 1, 3).map(String::toUpperCase).forEach(System.out::println); ## Flujos paralelos -Una ventaja de usar la abstracción Stream es que la biblioteca puede controlar internamente y eficazmente el paralelismo en la iteración. Puedes construir un flujo paralelo llamando al método `parallel`. El método `parallel` usa por debajo el API fork-join introducido en el JDK 7. Por defecto, creará tantos hilos como cpus tenga tu máquina. En el siguiente código, estamos agrupando números por hilos que los procesan. Aprenderás sobre las funciones `collect` y `groupingBy` en el capítulo 4. Por ahora, quedate con que te permiten agrupar elementos basados en una clave. +Una ventaja de usar la abstracción Stream es que la biblioteca puede controlar de forma interna y eficaz el paralelismo en la iteración. Puedes construir un flujo paralelo llamando al método `parallel()`. El método `parallel()` usa por debajo el API fork-join introducido en el JDK 7. Por defecto, creará tantos hilos como cpus tenga tu máquina. En el siguiente código, estamos agrupando números por hilos que los procesan. Aprenderás sobre las funciones `collect()` y `groupingBy()` en el capítulo 4. Por ahora, quedate con que te permiten agrupar elementos basados en una clave. ```java public class ParallelStreamExample { @@ -470,15 +475,17 @@ ForkJoinPool.commonPool-worker-3 >> [21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, ForkJoinPool.commonPool-worker-4 >> [91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145] ``` -Cada hilo no procesa el mismo número de elementos. Puedes controlar el tamaño del agrupamiento (`pool`) configurando una propiedad del sistema `System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "2")`. +Cada hilo no procesa el mismo número de elementos. Puedes controlar el tamaño del agrupamiento (`pool`) configurando una propiedad del sistema: + + `System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "2")`. -Otro ejemplo donde puede usar la operación `parallel` es cuando estás procesando una listsa de URLs. +Otro ejemplo donde puede usar la operación `parallel` es cuando estás procesando una lista de URLs. ```java String[] urls = {"https://www.google.co.in/", "https://twitter.com/", "http://www.facebook.com/"}; Arrays.stream(urls).parallel().map(url -> getUrlContent(url)).forEach(System.out::println); ``` -Si necesitas entender cuando usar flujos paralelos, te recomendaría leer este artículo de Doug Lea y otros compañeros de Java [http://gee.cs.oswego.edu/dl/html/StreamParallelGuidance.html](http://gee.cs.oswego.edu/dl/html/StreamParallelGuidance.html) para comprenderlo mucho mejor. +Si necesitas entender cuándo usar flujos paralelos, te recomendaría leer este artículo de Doug Lea y otros compañeros de Java [http://gee.cs.oswego.edu/dl/html/StreamParallelGuidance.html](http://gee.cs.oswego.edu/dl/html/StreamParallelGuidance.html) para comprenderlo mucho mejor. [![Analytics](https://ga-beacon.appspot.com/UA-74043032-1/malobato/java8-the-missing-tutorial/03-streams)](https://github.com/igrigorik/ga-beacon) diff --git a/es/04-collectors.md b/es/04-collectors.md index ee931f9..6e78c96 100644 --- a/es/04-collectors.md +++ b/es/04-collectors.md @@ -1,17 +1,17 @@ Collectors ------ -En el [día 2](https://github.com/malobato/java8-the-missing-tutorial/blob/master/03-streams.md), aprendiste que el API Stream puede ayudarte a trabajar con colecciones de manera declarativa. Observamos el método `collect`, que es una operación terminal que acumula el conjunto de resultados de una tubería de flujo en un `List`. `Collect` es una operación de reducción que reduce un flujo a un valor. El valor podría ser un `Collection`, un `Map` o un objeto valor. Puedes usar `collect` para obtener lo siguiente: +En el [capítulo 2](https://github.com/malobato/java8-the-missing-tutorial/blob/master/03-streams.md), aprendiste que el API Stream puede ayudarte a trabajar con colecciones de manera declarativa. Observamos el método `collect()`, que es una operación terminal que acumula el conjunto de resultados de una tubería de flujo en un `List`; es una operación de reducción que reduce un flujo a un valor. El valor podría ser un `Collection`, un `Map` o un objeto valor. Puedes usar `collect()` para obtener lo siguiente: -1. **Reducir un flujo a un simple valor:** El resultado de la ejecución del flujo se puede reducir a un simple valor. Un valor simple podría ser un `Collection` o un valor numérico como un int, double, etc o un objeto de valor personalizado. +1. **Reducir un flujo a un simple valor:** El resultado de la ejecución del flujo se puede reducir a un simple valor. Un valor simple podría ser una `Collection` o un valor numérico como un *int*, *double*, etc o un objeto de valor personalizado. 2. **Agrupar elementos en un flujo:** Agrupar todas las tareas de un flujo por tipo de tarea. El resultado será un `Map>` con cada entrada conteniendo el tipo de tarea y sus tareas asociadas. También puedes usar cualquier otra colección en vez de un `List`. Si no necesitas todas las tareas asociadas con un tipo de tarea, también puedes producir `Map`. Un ejemplo podría ser agrupar las tareas por tipo y obtener la primera tarea creada. -3. **Dividir los elementos de un flujo:** Puedes dividir un flujo en dos grupos -- tareas esperadas y completadas. +3. **Dividir los elementos de un flujo:** Puedes dividir un flujo en dos grupos: tareas esperadas y completadas. ## Collector en Acción -Para sentir el poder de `Collector` vamos a observar el ejemplo donde tenemos que agrupar tareas por su tipo. En Java 8, podemos conseguir agrupar por tipo de tarea con el siguiente código. **Por favor revisar el [día 2](https://github.com/malobato/java8-the-missing-tutorial/blob/master/02-lambdas.md) del blog donde hablamos sobre el ejemplo que seguimos en esta serie.** +Para demostrar el poder de `Collector` vamos a observar el ejemplo donde tenemos que agrupar tareas por su tipo. En Java 8, podemos conseguir agrupar por tipo de tarea con el siguiente código. **(Revisar el [capítulo 2](https://github.com/malobato/java8-the-missing-tutorial/blob/master/02-lambdas.md) donde hablamos sobre el ejemplo que seguimos en esta serie).** ```java private static Map> groupTasksByType(List tasks) { @@ -19,7 +19,7 @@ private static Map> groupTasksByType(List tasks) { } ``` -El código siguiente usa `groupingBy` de `Collector` definido en la clase de utilidad `Collectors`. Crea un mapa con clave `TaskType` y valor una lista que contiene todas las tareas con el mismo `TaskType`. Para conseguir lo mismo en Java 7 habría que escribir el siguiente código. +El código siguiente usa el método `groupingBy()` de `Collector` definido en la clase de utilidad `Collectors`. Crea un mapa con clave `TaskType` y valor una lista que contiene todas las tareas con el mismo `TaskType`. Para conseguir lo mismo en Java 7 habría que escribir el siguiente código: ```java public static void main(String[] args) { @@ -43,16 +43,16 @@ public static void main(String[] args) { ## Collectors: Operaciones de reducción comunes -La clase utilidad `Collectors` proporcinoa un montón de métodos estáticos de utilidad para crear acumuladores para la mayoría de casos de uso comunes como acumular elementos en una colección, agrupar y parciticionar elementos y resumir elementos de acuerdo a varios criterios. Cubriremos la mayoría de casos comunes de `Collector` en este blog. +La clase utilidad `Collectors` proporciona un montón de métodos estáticos de utilidad para crear **acumuladores** para la mayoría de casos de uso comunes como acumular elementos en una colección, agrupar y particionar elementos, y resumir elementos de acuerdo a varios criterios. Cubriremos la mayoría de casos comunes de `Collector` en este blog. ## Reducir a un simple valor -Como ya vimos, los acumuladores se pueden usar para recoger la salida de un flujo en una colección o generar un valor simple. +Como ya vimos, los **acumuladores** se pueden usar para recoger la salida de un flujo en una colección o generar un valor simple. ### Recoger datos en una lista -Vamos a escribir nuestro primer caso de prueba -- dada una lista de tareas queremos recoger sus títulos en una lista. +Vamos a escribir nuestro primer caso de prueba: dada una lista de tareas queremos recoger sus títulos en una lista. ```java import static java.util.stream.Collectors.toList; @@ -64,11 +64,11 @@ public class Example2_ReduceValue { } ``` -El acumulador `toList` usa el método de `List` `add` para añadir elementos dentro de la lista resultante. El acumulador `toList` usa un `ArrayList` como implementación de la lista. +El acumulador `toList()` usa el método `add()` de `List` para añadir elementos dentro de la lista resultante. Usa un `ArrayList` como implementación de la lista. ### Recoger datos en un conjunto -Si queremos estar seguros de que sólo recogemos títulos únicos y no nos preocupa el orden, podemos usar el acumulador `toSet`. +Si queremos estar seguros de que sólo recogemos títulos únicos y no nos preocupa el orden, podemos usar el acumulador `toSet()`. ```java import static java.util.stream.Collectors.toSet; @@ -78,11 +78,11 @@ public Set uniqueTitles(List tasks) { } ``` -El método `toSet` usa un `HashSet` como implementación del conjunto para guardar el resultado. +El método `toSet()` usa un `HashSet` como implementación del conjunto para guardar el resultado. ### Recoger datos en un mapa -Puedes convertir un flujo en un mapa usando el acumulador `toMap`. El acumulador `toMap` toma dos funciones de mapeado para extraer la llave y los valores del mapa. En el código siguiente `Task::getTitle` es una función que toma una tarea y produce una clave con un único título. **task -> task** es una expresión lambda que se devuelve a si misma. P. ej. la tarea en este caso. +Puedes convertir un flujo en un mapa usando el acumulador `toMap()`. El acumulador `toMap()` toma como parámetros dos funciones de mapeado para extraer la clave y los valores del mapa. En el código siguiente `Task::getTitle` es una función que toma una tarea y produce una clave con un único título. **task -> task** es una expresión lambda que se devuelve a si misma. P. ej. la tarea en este caso. ```java private static Map taskMap(List tasks) { @@ -90,7 +90,7 @@ private static Map taskMap(List tasks) { } ``` -Podemos mejorar el código anterior usando el método por defecto `identity` en el interfaz `Function` para generar código más limpio y que transmita mucho mejor la intención del programador. +Podemos mejorar el código anterior usando el método por defecto `identity()` en la interfaz `Function` para generar código más limpio y que transmita mucho mejor la intención del programador. ```java import static java.util.function.Function.identity; @@ -107,7 +107,7 @@ Exception in thread "main" java.lang.IllegalStateException: Duplicate key Task{t at java.util.stream.Collectors.lambda$throwingMerger$105(Collectors.java:133) ``` -Puedes controlar los duplicados usando otra variante de la función `toMap` que nos permite especificar una función de unión. La función de uníón permite al cliente especificar como quiere resolver la colisión entre valores asociados a la misma clave. En el código siguiente, sólo usaremos el último valor pero puedes escribti un algoritmo para resolver la colisión. +Puedes controlar los duplicados usando otra variante de la función `toMap()` que nos permite especificar una función de unión. La función de unión permite al cliente especificar como quiere resolver la colisión entre valores asociados a la misma clave. En el código siguiente, sólo nos quedamos con el último valor, pero puedes escribir un algoritmo para resolver la colisión. ```java private static Map taskMap_duplicates(List tasks) { @@ -115,7 +115,7 @@ private static Map taskMap_duplicates(List tasks) { } ``` -Puedes usar cualquier otra implementación de mapa usando la tercera variante del método `toMap`. Esto require que especifiques el `Map` `Supplier` que se usará para guardar el resultado. +Puedes usar cualquier otra implementación de mapa usando la tercera variante del método `toMap()`. Esto require que especifiques el `Map` `Supplier` que se usará para guardar el resultado. ``` public Map collectToMap(List tasks) { @@ -123,11 +123,11 @@ public Map collectToMap(List tasks) { } ``` -Similar al acumulador `toMap` también esta el acumulador `ToConcurrentMap` que produce un `ConcurrentMap` en vez de un `HashMap`. +Similar al acumulador `toMap()` también está el acumulador `toConcurrentMap()` que produce un `ConcurrentMap` en vez de un `HashMap`. ### Usando otras colecciones -Los acumuladores específicos como `toList` y `toSet` no te permiten especificar la implementación de la lista o del conjunto. Puedes usar el acumulador `toCollection` cuando quieras recoger el resultado en otros tipos de colecciones como se muestra a continuación. +Los acumuladores específicos como `toList()` y `toSet()` no te permiten especificar la implementación de la lista o del conjunto. Puedes usar el acumulador `toCollection()` cuando quieras recoger el resultado en otros tipos de colecciones como se muestra a continuación. ``` private static LinkedHashSet collectToLinkedHaskSet(List tasks) { @@ -165,10 +165,11 @@ Uno de los casos de uso más comunes es agrupar elementos. Vamos a ver varios ej ### Ejemplo 1: Agrupando tareas por tipo -Vamos a ver el ejemplo mostrado abajo donde queremos agrupar todas las tareas basándonos en su `TaskType`. Puedes realizar esta tarea de una forma muy sencilla usando el acumulador `groupingBy` de la clase de utilidad `Collectors`. Puedes acortarlo más usando referencias a método e importaciones estáticas. +Vamos a ver el ejemplo mostrado abajo donde queremos agrupar todas las tareas basándonos en su `TaskType`. Puedes realizar esta tarea de una forma muy sencilla usando el acumulador `groupingBy()` de la clase de utilidad `Collectors`. Puedes acortarlo más usando referencias a método e importaciones estáticas. ```java import static java.util.stream.Collectors.groupingBy; + private static Map> groupTasksByType(List tasks) { return tasks.stream().collect(groupingBy(Task::getType)); } @@ -230,7 +231,7 @@ private static Map>> groupTasksByTypeAndCrea ## Dividiendo en partes -Hay veces en las que quieres partir un conjunto de datos en dos basándote en un predicado. Por ejemplo, podemos partir tareas en dos grupos definiendo una función de reparto que parta las tareas en dos grupos -- uno con fecha de vencimiento anterior a hoy y otro con fecha de vencimiento posterior a hoy. +Hay veces en las que quieres partir un conjunto de datos en dos basándote en un predicado. Por ejemplo, podemos partir tareas en dos grupos definiendo una función de reparto que parta las tareas en dos grupos: uno con fecha de vencimiento anterior a hoy y otro con fecha de vencimiento posterior a hoy. ```java private static Map> partitionOldAndFutureTasks(List tasks) { @@ -240,7 +241,7 @@ private static Map> partitionOldAndFutureTasks(List ta ## Generando estadísticas -Otro grupo de acumuladores que son muy útiles son los acumuladores que producen estadísticas. Estos trabajan con tipos de datos elementales como int, double, long y pueden usarse para generar estadísticar como la que se muestra a continuación. +Otro grupo de acumuladores que son muy útiles son los acumuladores que producen estadísticas. Estos trabajan con tipos de datos elementales como int, double, long y pueden usarse para generar estadísticas como la que se muestra a continuación. ```java IntSummaryStatistics summaryStatistics = tasks.stream().map(Task::getTitle).collect(summarizingInt(String::length)); diff --git a/es/05-optionals.md b/es/05-optionals.md index 921d057..d164180 100644 --- a/es/05-optionals.md +++ b/es/05-optionals.md @@ -1,21 +1,21 @@ Optionals ---- -Cada programador de Java, ya sea principiante, novato o experto, ha experimentado en su vida un `NullPointerException`. Es un hecho veraz que ningún programador de Java puede negar. Todos hemos malgastado o perdido muchas horas tratando de corregir errores debidos a un `NullPointerException`. De acuerdo al JavaDoc de `NullPointerException`, ***Una excepción NullPointerException se arroja cuando una aplicación intenta usar un nulo en vez de un objeto.*** Esto quiere decir que si invocamos un método o intentamos acceder a una propiedad sobre una referencia ***nula***, nuestro código reventará y se lanzará un `NullPointerException`. En este capítulo, aprenderás como escribir código libre de nulos usando el `Optional` de Java 8. +Cada programador de Java, ya sea principiante, novato o experto, ha experimentado en su vida un `NullPointerException`. Es un hecho que ningún programador de Java puede negar. Todos hemos malgastado o perdido muchas horas tratando de corregir errores debidos a un `NullPointerException`. De acuerdo al JavaDoc de `NullPointerException`, ***Una excepción NullPointerException se arroja cuando una aplicación intenta usar un nulo en vez de un objeto.*** Esto quiere decir que si invocamos un método o intentamos acceder a una propiedad sobre una referencia ***nula***, nuestro código reventará y se lanzará un `NullPointerException`. En este capítulo, aprenderás como escribir código libre de nulos usando el `Optional` de Java 8. -> Como comentario, si miras en el JavaDoc de `NullPointerException` encontrarás que el autor de esta excepción esta ***sin atribuir***. Si se usa, el autor es desconocido o está sin atribuir, quiere decir que nadie quiere responsabilizarse del `NullPointerException` ;). +> Como comentario, si miras en el JavaDoc de `NullPointerException` encontrarás que el autor de esta excepción está ***sin atribuir***. Si se usa, el autor es desconocido o está sin atribuir, quiere decir que nadie quiere responsabilizarse del `NullPointerException` ;). ## ¿Qué son las referencias nulas? En el 2009 en la conferencia QCon ***[Sir Tony Hoare](https://en.wikipedia.org/wiki/Tony_Hoare)*** -declaró que el inventó el tipo de referencia nulo mientras diseñaba el lenguaje de programación ***ALGOL W***. El nulo fue diseñado para indicar la ausencia de un valor. Designó a las *referencias nulas* como *el error del billón de dolares*. Puedes ver el video completo de su presentación en [Infoq](http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare). +declaró que él inventó el tipo de referencia nulo mientras diseñaba el lenguaje de programación ***ALGOL W***. El nulo fue diseñado para indicar la ausencia de un valor. Designó a las *referencias nulas* como *el error del billón de dolares*. Puedes ver el video completo de su presentación en [Infoq](http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare). La mayoría de los lenguajes de programación, como C, C++, C#, Java, Scala, etc, tienen tipos nulos como parte de su sistema de tipado lo que permite establecer como valor un **Nulo** en vez de otros posibles valores de tipos de datos. ## ¿Por qué las referencias nulas son malas? -Vamos a ver el siguiente ejemplo sobre las clases del dominio de gestión de tareas. Nuestro modelo de dominio es muy sencillo, sólo tiene dos clases -- Task y User. Una tarea se puede asignar a un usuario. +Vamos a ver el siguiente ejemplo sobre las clases del dominio de gestión de tareas. Nuestro modelo de dominio es muy sencillo, sólo tiene dos clases: Task y User. Una tarea se puede asignar a un usuario. > El código de esta sección está en el [paquete ch05](https://github.com/shekhargulati/java8-the-missing-tutorial/tree/master/code/src/main/java/com/shekhargulati/java8_tutorial/ch05). @@ -86,7 +86,7 @@ public String taskAssignedTo(String taskId) { } ``` -El problema más grave del código mostrado es que la ausencia de un valor no es visible en el API. P. ej. si `task` no está asignada a algún usuario el código lanzará la exception `NullPointerException` cuando se invoque a `getAssignedTo`. `taskRepository.find(taskId)` y `taskRepository.find(taskId).getAssignedTo()` podrían devolver un `null`. Esto fuerza a los clientes del API a programar de manera defensiva y comprobar los nulos como se muestra a conituación. +El problema más grave del código mostrado es que la ausencia de un valor no es visible en el API. P. ej. si `task` no está asignada a algún usuario el código lanzará la excepción `NullPointerException` cuando se invoque a `getAssignedTo()`. Y `taskRepository.find(taskId)` también podría devolver un `null`. Esto fuerza a los clientes del API a programar de manera defensiva y comprobar los nulos como se muestra a continuación. ```java public String taskAssignedTo(String taskId) throws TaskNotFoundException { @@ -116,7 +116,7 @@ public class NullUser extends User { } ``` -Ahora si que podríamos devolver un `NullUser` cuando no haya asignado un usuario a una tarea. Podemos cambiar el método `getAssignedTo` para que devuelve un `NullUser` cuando no haya asignado un usuario a la tarea. +Ahora sí que podríamos devolver un `NullUser` cuando no haya asignado un usuario a una tarea. Podemos cambiar el método `getAssignedTo` para que devuelva un `NullUser` cuando no haya asignado un usuario a la tarea. ```java public User getAssignedTo() { @@ -124,7 +124,7 @@ public User getAssignedTo() { } ``` -Ahora el código del cliente se puede simplificar para que no compruebe los nulos como sigue. En este ejemplo, no tiene sentido usar el patrón Objeto Nulo en `Task`, ya que la ausencia de la tarea en el repositorio sería una situación de excepción. Además, al añadir `TaskNotFoundException` en la sección de `throws`, hemos hecho explicito para el cliente que el código puede arrojar una excepción. +Ahora el código del cliente se puede simplificar para que no compruebe los nulos como sigue. En este ejemplo, no tiene sentido usar el patrón Objeto Nulo en `Task`, ya que la ausencia de la tarea en el repositorio sería una situación de excepción. Además, al añadir `TaskNotFoundException` en la sección de `throws`, hemos hecho explícito para el cliente que el código puede arrojar una excepción. ```java public String taskAssignedTo(String taskId) throws TaskNotFoundException { @@ -138,7 +138,7 @@ public String taskAssignedTo(String taskId) throws TaskNotFoundException { ## Java 8 -- Presentación del tipo de dato Optional -Java 8 presenta un nuevo tipo de dato ***java.util.Optional*** que encapsula un valor vacío. Clarifica la intención del API. Si una función devuelve un valor de tipo Optional le dice al cliente que podría no devolver un valor. El uso del tipo de datos `Optional` hace explícito al cliente del API cuando debería esperar un valor opcional. Cuando usas el tipo `Optional`, como desarrollador, haces visible a través del sistema de tipos que el valor puede no estar presente y el cliente puede trabajar con él limpiamente. El propósito de usar el tipo `Optional` es ayudar a los diseñadores de APIs a hacer visible a sus cliente si deberían de esperar un valor opcional o no mirando la firma del método. +Java 8 presenta un nuevo tipo de dato ***java.util.Optional*** que encapsula un valor vacío. Aclara la intención del API. Si una función devuelve un valor de tipo Optional le dice al cliente que podría no devolver un valor. Cuando usas el tipo `Optional`, como desarrollador, haces visible a través del sistema de tipos que el valor puede no estar presente y el cliente puede trabajar con él limpiamente. El propósito de usar el tipo `Optional` es ayudar a los diseñadores de APIs a hacer visible a sus clientes de forma explícita si deberían de esperar un valor opcional o no mirando la firma del método. Vamos a actualizar nuestro modelo de dominio para reflejar valores opcionales. @@ -202,13 +202,13 @@ public class User { } ``` -El uso del tipo de dato `Optional` en el modelo de datos hace explícito que `Task` referencia a un ***Optional*** y que ***User*** tiene una dirección **Optional**. Ahora quien quiera tratar de trabajar con `assignedTo` debería saber que podría no estar presente y lo podría controlar de manera declarativa. Hablaremos de los métodos `Optional.empty` y `Optional.of` en la próxima sección. +El uso del tipo de dato `Optional` en el modelo de datos hace explícito que `Task` referencia a un ***Optional*** y también que `User` tiene una dirección **Optional**. Ahora quien quiera tratar de trabajar con `assignedTo()` debería saber que podría no estar presente y lo podría controlar de manera declarativa. Hablaremos de los métodos `Optional.empty()` y `Optional.of()` en la próxima sección. ## Trabajando con métodos de creación en el API java.util.Optional En el modelo de dominio mostrado arriba, usamos un par de métodos de creación de la clase `Optional` pero no hablé sobre ellos. Ahora vamos a hablar sobre tres métodos de creación que forman parte del API `Optional`. -* **Optional.empty**: Se usa para crear un `Optional` cuando no existe un valor como hicimos arriba en el constructor `this.assignedTo = Optional.empty();`. +* **Optional.empty()**: Se usa para crear un `Optional` cuando no existe un valor, como hicimos arriba en el constructor `this.assignedTo = Optional.empty();`. * **Optional.of(T value)**: Se usa para crear un `Optional` a partir de un valor no nulo. Lanza una excepción `NullPointerException` si el valor es nulo. Lo usamos anteriormente en `this.address = Optional.of(address);`. @@ -237,47 +237,47 @@ Se puede pensar en `Optional` como un flujo de un único elemento. Tiene método ### Obtener el título de una tarea -Para leer el valor del título de una tarea escribiríamos el siguiente código. La función `map` se usa para transformar de ***Optional*** a ***Optional***. El método `orElseThrow` se usa para lanzar una excepción personalizada de negocioo cuando no se encuentra la tarea. +Para leer el valor del título de una tarea escribiríamos el siguiente código. La función `map()` se usa para transformar de `Optional` a `Optional`. El método `orElseThrow()` se usa para lanzar una excepción personalizada de negocio cuando no se encuentra la tarea. ```java public String taskTitle(String taskId) { return taskRepository. - find(taskId). - map(Task::getTitle). - orElseThrow(() -> new TaskNotFoundException(String.format("No task exist for id '%s'",taskId))); + find(taskId). + map(Task::getTitle). + orElseThrow(() -> new TaskNotFoundException(String.format("No task exist for id '%s'",taskId))); } ``` -Existen tres variantes del método `orElse`: +Existen tres variantes del método `orElse()`: 1. **orElse(T t)**: Se usa para devolver un valor cuando exista o el valor que se le pasa como parámetro. P. ej. `Optional.ofNullable(null).orElse("NoValue")` devolverá `NoValue` ya que no existe el valor. -2. **orElseGet**: Devolverá el valor si está presente sino generará un nuevo valor resultado de invocar el método `get` de `Supplier`. Por ejemplo, `Optional.ofNullable(null).orElseGet(() -> UUID.randomUUID().toString()` se usaría para generar valores de forma perezosa cuando no exista un valor. +2. **orElseGet**: Devolverá el valor si está presente, y si no generará un nuevo valor resultado de invocar el método `get()` de `Supplier`. Por ejemplo, `Optional.ofNullable(null).orElseGet(() -> UUID.randomUUID().toString()` se usaría para generar valores de forma perezosa cuando no exista un valor. 3. **orElseThrow**: Esto permite a los clientes lanzar sus propias excepciones personalizadas cuando no exista un valor. -El método `find` mostrado en el ejemplo de antes devuelve un `Optional` que el cliente puede usar para obtener el valor. Supón que queremos obtener el título de una tarea a partir del `Optional`, podemos hacerlo usando la función `map` como se muestra a continuación. +El método `find()` mostrado en el ejemplo de antes devuelve un `Optional` que el cliente puede usar para obtener el valor. Supón que queremos obtener el título de una tarea a partir del `Optional`, podemos hacerlo usando la función `map()` como se muestra a continuación. ### Obtener el nombre del usuario asignado -Para obtener el nombre del usuario que está asignado a una tarea podemos usar el método `flatMap` de la siguiente manera. +Para obtener el nombre del usuario que está asignado a una tarea podemos usar el método `flatMap()` de la siguiente manera. ```java public String taskAssignedTo(String taskId) { return taskRepository. - find(taskId). - flatMap(task -> task.getAssignedTo().map(user -> user.getUsername())). - orElse("NotAssigned"); + find(taskId). + flatMap(task -> task.getAssignedTo().map(user -> user.getUsername())). + orElse("NotAssigned"); } ``` ### Filtrar con Optional -La tercera operación, como la del API de `Stream`, soportada por `Optional` es `filter`, que te permite filtrar un `Optional`por una propiedad como se muestra en el siguiente ejemplo. +La tercera operación, como la del API de `Stream`, soportada por `Optional` es `filter`, que te permite filtrar un `Optional` por una propiedad como se muestra en el siguiente ejemplo. ```java public boolean isTaskDueToday(Optional task) { - return task.flatMap(Task::getDueOn).filter(d -> d.isEqual(LocalDate.now())).isPresent(); + return task.flatMap(Task::getDueOn).filter(d -> d.isEqual(LocalDate.now())).isPresent(); } ``` [![Analytics](https://ga-beacon.appspot.com/UA-74043032-1/malobato/java8-the-missing-tutorial/05-optionals)](https://github.com/igrigorik/ga-beacon) diff --git a/es/06-map.md b/es/06-map.md index 8310b6c..115edd3 100644 --- a/es/06-map.md +++ b/es/06-map.md @@ -1,11 +1,11 @@ Mejoras en Map --------- -Map es una de las estructuras de datos más importantes. En Java 8, se han añadido un montón de mejoras al API Map que harán más fácil el trabajar con él. Veremos todas estas mejoras una a una. Cada característica se mostrará con su correspondiente caso de prueba JUnit. +`Map` es una de las estructuras de datos más importantes. En Java 8, se han añadido un montón de mejoras al API Map que harán más fácil el trabajar con él. Veremos todas estas mejoras una a una. Cada característica se mostrará con su correspondiente caso de prueba JUnit. ## Crear un Map a partir de un List -La mayoría de las veces queremos crear un mapa a partir de datos ya existentes. Vamos a suponer que tenemos una lista de tareas, cada tarea tiene un identificador y otros datos asociados como el título, la descripción, etc. +La mayoría de las veces queremos crear un mapa a partir de datos ya existentes. Vamos a suponer que tenemos una lista de tareas; cada tarea tiene un identificador y otros datos asociados como el título, la descripción, etc. ```java import static java.util.function.Function.identity; @@ -26,7 +26,7 @@ public void shouldCreateMapFromTaskList() throws Exception { ## Usar una implementación diferente de Map -La implementación por defecto usada por `Collectors.toMap` es `HashMap`. Puedes especificar tu propia implementación de Map suministrando un proveedor. +La implementación por defecto usada por `Collectors.toMap()` es `HashMap`. Puedes especificar tu propia implementación de Map suministrando un proveedor. ```java @Test @@ -45,7 +45,7 @@ public void shouldCreateLinkedMapFromTaskList() throws Exception { ## Manejando duplicados -Una cosa que pasamos por alto en el último ejemplo es que pasaría si hubiese duplicados. Para controlar los duplicados tenemos un argumento. +Una cosa que pasamos por alto en el último ejemplo es lo que pasaría si hubiese duplicados. Para controlar los duplicados tenemos el argumento extra. ```java @Test @@ -61,13 +61,13 @@ public void shouldHandleTaskListWithDuplicates() throws Exception { } ``` -El test fallará. +El test fallará: ``` java.lang.IllegalStateException: Duplicate key Task{title='Write blog on Java 8 Map improvements', type=BLOGGING} ``` -Puedes controlar el error especificando tu función de unión. +Puedes controlar el error especificando tu función de unión: ```java @Test diff --git a/es/08-date-time-api.md b/es/08-date-time-api.md index 9cc7c30..97c2855 100644 --- a/es/08-date-time-api.md +++ b/es/08-date-time-api.md @@ -1,11 +1,11 @@ API de Fecha y Hora ------- -Hasta ahora en este [libro](https://github.com/malobato/java8-the-missing-tutorial) nos hemos centrado en la parte [funcional](02-lambdas.md)) y en [aspectos](03-streams.md) de Java 8, además hemos visto como diseñar mejores APIs usando [Optional](05-optionals.md) y [métodos por defecto y estáticos en interfaces](01-default-static-interface-methods.md). En este capítulo, aprenderemos sobre otro API nuevo que cambiará la forma en la que trabajamos con fechas -- el API de Fecha y Hora. Casi todos los desarrolladores de Java estarán de acuerdo en que el soporte de fecha y hora anterior a Java 8 esta lejos de ser ideal y la mayoría de las veces tenemos que hacer uso de bibliotecas de terceros como [Joda-Time](http://www.joda.org/joda-time/) en nuestras aplicaciones. El nuevo API de fecha y hora esta muy influenciado por el API Joda-Time y si lo has usado ten sentirás como en casa. +Hasta ahora en este [libro](https://github.com/malobato/java8-the-missing-tutorial) nos hemos centrado en la parte [funcional](02-lambdas.md) y en [aspectos](03-streams.md) de Java 8. Además hemos visto cómo diseñar mejores APIs usando [Optional](05-optionals.md) y [métodos por defecto y estáticos en interfaces](01-default-static-interface-methods.md). En este capítulo, aprenderemos sobre otro API nuevo que cambiará la forma en la que trabajamos con fechas: el API de Fecha y Hora. Casi todos los desarrolladores de Java estarán de acuerdo en que el soporte de fecha y hora anterior a Java 8 esta lejos de ser ideal y la mayoría de las veces tenemos que hacer uso de bibliotecas de terceros como [Joda-Time](http://www.joda.org/joda-time/) en nuestras aplicaciones. El nuevo API de fecha y hora esta muy influenciado por el API Joda-Time y si lo has usado te sentirás como en casa. -## ¿Qué tiene de malo el API de fecha existente? +## ¿Qué tiene de malo el API de fecha anterior? -Antes de aprender sobre el nuevo API de Fecha y Hora vamos a explicar porque no nos gusta el API de fecha actual. Observa el código de abajo y trata de responder que imprimirá. +Antes de aprender sobre el nuevo API de Fecha y Hora vamos a explicar porque no nos gusta el API de fecha anterior. Observa el código de abajo y trata de responder que imprimirá. ```java import java.util.Date; @@ -31,17 +31,17 @@ El código anterior tiene los siguientes problemas: 4. El año comienza en 1900 y dado que el mes también es cíclico el año se convierte en `1900 + 12 + 1 == 1913`. ¡Imagínate! -5. ¿Quién pidió la hora? Acabo de pedir la fecha y el programa también me imrime la hora. +5. ¿Quién pidió la hora? Acabo de pedir la fecha y el programa también me imprime la hora. -6. ¿Por qué esta la zona horaria? ¿Quién la pidió? La zona horaria es la zona horaria por defecto de la JVM, IST, Indian Standard Time en este ejemplo. +6. ¿Por qué está la zona horaria? ¿Quién la pidió? La zona horaria es la zona horaria por defecto de la JVM, IST, Indian Standard Time en este ejemplo. -> El API de fecha tiene cerca de 20 años y fue presentado con el JDK 1.0. Uno de los autores originales del API de fecha no es otro que el mismo James Gosling -- El padre del lenguaje de programación Java. +> El API de fecha tiene cerca de 20 años y fue presentado con el JDK 1.0. Uno de los autores originales del API de fecha no es otro que el mismo James Gosling; el padre del lenguaje de programación Java. -Existen muchos otros problemas de diseño del API de fecha como la mutabilidad, jerarquía de clases separadas para SQL, etc. En el JDK 1.1 se hizó un esfuerzo por proporcionar un mejor API, p. ej. `Calendar` pero también estaba plagado de problemas similares de mutabilidad e índices comenzando por 0. +Existen muchos otros problemas de diseño del API de fecha como la mutabilidad, jerarquía de clases separadas para SQL, etc. En el JDK 1.1 se hizo un esfuerzo por proporcionar una mejor API con `Calendar` pero también estaba plagado de problemas similares de mutabilidad e índices comenzando por 0. -## El API de Fecha y Hora de Java +## El API de Fecha y Hora de Java 8 -El API de Fecha y Hora de Java 8 fue desarrollado como parte del JSR-310 y se encuentra en el paquete `java.time`. El API usa principio de **diseño orientado a dominio** con clases de dominio como LocalDate o LocalTime que se usan para resolver problemas relacionados a sus dominios específicos de fecha y hora. Esto hace que el API sea claro y fácil de ententer. El otro principo de diseño usado es el de **inmutabilidad**. Todas las clases del núcleo de `java.time` son inmutables por lo que evitan problemas de seguridad en hilos. +El API de Fecha y Hora de Java 8 fue desarrollado como parte del JSR-310 y se encuentra en el paquete `java.time`. El API usa el principio de **diseño orientado a dominio** con clases de dominio como `LocalDate` o `LocalTime` que se usan para resolver problemas relacionados a sus dominios específicos de fecha y hora. Esto hace que el API sea claro y fácil de entender. El otro principio de diseño usado es el de **inmutabilidad**. Todas las clases del núcleo de `java.time` son inmutables por lo que evitan problemas de seguridad en hilos. ## Comenzando con el API de Fecha y Hora @@ -73,7 +73,7 @@ public class DateTimeExamplesTest { } ``` -`LocalDate` has a static factory method `of` that takes year, month, and date and gives you a `LocalDate`. To make this test pass, we will write `dateOfBirth` method in `AbdulKalam` class using `of` method as shown below. +`LocalDate` has a static factory method `of()` that takes year, month, and date and gives you a `LocalDate`. To make this test pass, we will write `dateOfBirth` method in `AbdulKalam` class using `of()` method as shown below. ```java import java.time.LocalDate; @@ -86,9 +86,9 @@ public class AbdulKalam { } ``` -There is an overloaded `of` method that takes month as integer instead of `Month` enum. I recommend using `Month` enum as it is more readable and clear. There are two other static factory methods to create `LocalDate` instances -- `ofYearDay` and `ofEpochDay`. +There is an overloaded `of()` method that takes month as integer instead of `Month` enum. I recommend using `Month` enum as it is more readable and clear. There are two other static factory methods to create `LocalDate` instances: `ofYearDay()` and `ofEpochDay()`. -The `ofYearDay` creates LocalDate instance from the year and day of year for example March 31st 2015 is the 90th day in 2015 so we can create LocalDate using `LocalDate.ofYearDay(2015, 90)`. +The `ofYearDay()` creates LocalDate instance from the year and day of year for example March 31st 2015 is the 90th day in 2015 so we can create LocalDate using `LocalDate.ofYearDay(2015, 90)`. ```java LocalDate january_21st = LocalDate.ofYearDay(2015, 21); @@ -97,7 +97,7 @@ LocalDate march_31st = LocalDate.ofYearDay(2015, 90); System.out.println(march_31st); // 2015-03-31 ``` -The `ofEpochDay` creates LocalDate instance using the epoch day count. The starting value of epoch is `1970-01-01`. So, `LocalDate.ofEpochDay(1)` will give `1970-01-02`. +The `ofEpochDay()` creates LocalDate instance using the epoch day count. The starting value of epoch is `1970-01-01`. So, `LocalDate.ofEpochDay(1)` will give `1970-01-02`. LocalDate instance provide many accessor methods to access different fields like year, month, dayOfWeek, etc. @@ -112,7 +112,7 @@ public void kalamWasBornOn15October1931() throws Exception { } ``` -You can create current date from the system clock using `now` static factory method. +You can create current date from the system clock using `now()` static factory method. ```java LocalDate.now() @@ -128,7 +128,7 @@ public void kalamWasBornAt0115() throws Exception { } ``` -`LocalTime` class is used to work with time. Just like `LocalDate`, it also provides static factory methods for creating its instances. We will use the `of` static factory method giving it hour and minute and it will return LocalTime as shown below. +`LocalTime` class is used to work with time. Just like `LocalDate`, it also provides static factory methods for creating its instances. We will use the `of()` static factory method giving it hour and minute and it will return LocalTime as shown below. ```java public LocalTime timeOfBirth() { @@ -136,19 +136,19 @@ public LocalTime timeOfBirth() { } ``` -There are other overloaded variants of `of` method that can take second and nanosecond. +There are other overloaded variants of `of()` method that can take second and nanosecond. > LocalTime is represented to nanosecond precision. -You can print the current time of the system clock using `now` method as shown below. +You can print the current time of the system clock using `now()` method as shown below. ```java LocalTime.now() ``` -You can also create instances of `LocalTime` from seconds of day or nanosecond of day using `ofSecondOfDay` and `ofNanoOfDay` static factory methods. +You can also create instances of `LocalTime` from seconds of day or nanosecond of day using `ofSecondOfDay()` and `ofNanoOfDay()` static factory methods. -Similar to `LocalDate` `LocalTime` also provide accessor for its field as shown below. +Similar to `LocalDate`, `LocalTime` also provides accessor for its fields as shown below. ```java @Test @@ -162,7 +162,7 @@ public void kalamWasBornAt0115() throws Exception { ### Kalam was born on 15 October at 01:15 am -When you want to represent both date and time together then you can use `LocalDateTime`. LocalDateTime also provides many static factory methods to create its instances. We can use `of` factory method that takes a `LocalDate` and `LocalTime` and gives `LocalDateTime` instance as shown below. +When you want to represent both date and time together then you can use `LocalDateTime`. LocalDateTime also provides many static factory methods to create its instances. We can use `of()` factory method that takes a `LocalDate` and `LocalTime` and gives `LocalDateTime` instance as shown below. ```java public LocalDateTime dateOfBirthAndTime() { @@ -196,7 +196,7 @@ public void kalam50thBirthDayWasOnThursday() throws Exception { } ``` -We can use `dateOfBirth` method that we wrote earlier with `plusYears` on `LocalDate` instance to achieve this as shown below. +We can use `dateOfBirth` method that we wrote earlier with `plusYears()` on `LocalDate` instance to achieve this as shown below. ```java public DayOfWeek dayOfBirthAtAge(final int age) { @@ -204,20 +204,20 @@ public DayOfWeek dayOfBirthAtAge(final int age) { } ``` -There are similar `plus*` variants for adding days, months, weeks to the value. +There are similar `plus*()` variants for adding days, months, weeks to the value. -Similar to `plus` methods there are `minus` methods that allow you minus year, days, months from a `LocalDate` instance. +Similar to `plus*()` methods there are `minus()` methods that allow you minus year, days, months from a `LocalDate` instance. ```java LocalDate today = LocalDate.now(); LocalDate yesterday = today.minusDays(1); ``` -> Just like LocalDate LocalTime and LocalDateTime also provide similar `plus*` and `minus*` methods. +> Just like LocalDate, LocalTime and LocalDateTime also provide similar `plus*()` and `minus*()` methods. ### List all Kalam's birthdate DayOfWeek -For this use-case, we will create an infinite stream of `LocalDate` starting from the Kalam's date of birth using the `Stream.iterate` method. `Stream.iterate` method takes a starting value and a function that allows you to work on the initial seed value and return another value. We just incremented the year by 1 and return next year birthdate. Then we transformed `LocalDate` to `DayOfWeek` to get the desired output value. Finally, we limited our result set to the provided limit and collected Stream result into a List. +For this use-case, we will create an infinite stream of `LocalDate` starting from the Kalam's date of birth using the `Stream.iterate()` method. This method takes a starting value and a function that allows you to work on the initial seed value and return another value. We just incremented the year by 1 and return next year birthdate. Then we transformed `LocalDate` to `DayOfWeek` to get the desired output value. Finally, we limited our result set to the provided limit and collected Stream result into a List. ```java public List allBirthDateDayOfWeeks(int limit) { @@ -246,7 +246,7 @@ public void kalamLived30601Days() throws Exception { } ``` -To calculate number of days kalam lived we can use `Duration` class. `Duration` has a factory method that takes two `LocalTime`, or `LocalDateTime` or `Instant` and gives a duration. The duration can then be converted to days, hours, seconds, etc. +To calculate the number of days kalam lived we can use `Duration` class. `Duration` has a factory method `between()` that takes two `LocalTime`, or `LocalDateTime` or `Instant` and gives a duration. The duration can then be converted to days, hours, seconds, etc. ```java public Duration kalamLifeDuration() { @@ -271,7 +271,7 @@ public void kalamLifePeriod() throws Exception { } ``` -We can use `Period` class to calculate number of years, months, and days kalam lived as shown below. Period's `between` method works with `LocalDate` only. +We can use `Period` class to calculate number of years, months, and days kalam lived as shown below. Period's `between()` method works with `LocalDate` only. ```java public Period kalamLifePeriod() { @@ -297,7 +297,7 @@ public void kalamDateOfBirthFormattedInIndianDateFormat() throws Exception { } ``` -The `formatDateofBirth` method uses `DateTimeFormatter` `ofPattern` method to create a new formatter using the specified pattern. All the main main date-time classes provide two methods - one for formatting, `format(DateTimeFormatter formatter)`, and one for parsing, `parse(CharSequence text, DateTimeFormatter formatter)`. +The `formatDateofBirth()` method uses `DateTimeFormatter`'s `ofPattern()` method to create a new formatter using the specified pattern. All the main main date-time classes provide two methods: one for formatting, `format(DateTimeFormatter formatter)`, and one for parsing, `parse(CharSequence text, DateTimeFormatter formatter)`. ```java public String formatDateOfBirth(final String pattern) { @@ -331,7 +331,7 @@ public void shouldParseKalamDateOfBirthAndTimeToLocalDateTime() throws Exception } ``` -We will again use `DateTimeFormatter` `ofPattern` method to create a new `DateTimeFormatter` and then use the `parse` method of `LocalDateTime` to create a new instance of `LocalDateTime` as shown below. +We will again use `DateTimeFormatter`'s `ofPattern()` method to create a new `DateTimeFormatter` and then use the `parse()` method of `LocalDateTime` to create a new instance of `LocalDateTime` as shown below. ```java public LocalDateTime parseDateOfBirthAndTime(String input) { @@ -341,7 +341,7 @@ public LocalDateTime parseDateOfBirthAndTime(String input) { ## Advance date time manipulation with TemporalAdjusters -In `Manipulating dates` section, we learnt how we can use `plus*` and `minus*` methods to manipulate dates. Those methods are suitable for simple manipulation operations like adding or subtracting days, months, or years. Sometimes, we need to perform advance date time manipulation such as adjusting date to first day of next month or adjusting date to next working day or adjusting date to next public holiday then we can use `TemporalAdjusters` to meet our needs. Java 8 comes bundled with many predefined temporal adjusters for common scenarios. These temporal adjusters are available as static factory methods inside the `TemporalAdjusters` class. +In the **Manipulating dates** section, we learnt how we can use `plus*()` and `minus*()` methods to manipulate dates. Those methods are suitable for simple manipulation operations like adding or subtracting days, months, or years. Sometimes, we need to perform advance date time manipulation such as adjusting date to first day of next month or adjusting date to next working day or adjusting date to next public holiday then we can use `TemporalAdjusters` to meet our needs. Java 8 comes bundled with many predefined temporal adjusters for common scenarios. These temporal adjusters are available as static factory methods inside the `TemporalAdjusters` class. ```java LocalDate date = LocalDate.of(2015, Month.OCTOBER, 25); @@ -356,9 +356,9 @@ System.out.println(firstDayOfNextMonth);// This will print 2015-11-01 LocalDate lastFridayOfMonth = date.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY)); System.out.println(lastFridayOfMonth); // This will print 2015-10-30 ``` -* **firstDayOfMonth** creates a new date set to first day of the current month. -* **firstDayOfNextMonth** creates a new date set to first day of next month. -* **lastInMonth** creates a new date in the same month with the last matching day-of-week. For example, last Friday in October. +* `firstDayOfMonth()` creates a new date set to first day of the current month. +* `firstDayOfNextMonth()` creates a new date set to first day of next month. +* `lastInMonth()` creates a new date in the same month with the last matching day-of-week. For example, last Friday in October. I have not covered all the temporal-adjusters please refer to the documentation for the same. ![TemporalAdjusters](https://whyjava.files.wordpress.com/2015/10/temporal-adjusters.png) diff --git a/es/12-annotations.md b/es/12-annotations.md index 5963963..417ab29 100644 --- a/es/12-annotations.md +++ b/es/12-annotations.md @@ -70,7 +70,7 @@ public void manage() { } ``` -If you have to find all the repeatable annotations on a method then you can use `getAnnotationsByType` method that is now available on `java.lang.Class` and `java.lang.reflect.Method`. To print all the vm names, you can write code as shown below. +If you have to find all the repeatable annotations on a method then you can use `getAnnotationsByType()` method that is now available on `java.lang.Class` and `java.lang.reflect.Method`. To print all the vm names, you can write code as shown below. ```java CreateVm[] createVms = VmManager.class.getMethod("manage").getAnnotationsByType(CreateVm.class);