JavaRush /Blog Java /Random-ES /Funciones de Java 8: la guía definitiva (Parte 2)
0xFF
Nivel 9
Донецк

Funciones de Java 8: la guía definitiva (Parte 2)

Publicado en el grupo Random-ES
La segunda parte de la traducción del artículo Características de Java 8: la guía ULTIMATE . La primera parte está aquí (el enlace puede cambiar). Funciones de Java 8: la guía definitiva (Parte 2) - 1

5. Nuevas funciones en las bibliotecas de Java 8

Java 8 ha agregado muchas clases nuevas y ha ampliado las existentes para admitir mejor la concurrencia moderna, la programación funcional, la fecha/hora y más.

5.1. Clase Opcional

La famosa NullPointerException es, con diferencia, la causa más común de fallos en las aplicaciones Java. Hace mucho tiempo, el excelente proyecto Guava de Google se presentó Optionalcomo una solución NullPointerException, evitando así que el código se contamine con comprobaciones nulas y, como resultado, fomentando la escritura de código más limpio. La clase Guava inspirada en Google Optionalahora es parte de Java 8. OptionalEs solo un contenedor: puede contener un valor o algún tipo Т, o simplemente ser nulo. Proporciona muchos métodos útiles para que las comprobaciones nulas explícitas ya no estén justificadas. Consulte la documentación oficial para obtener información más detallada. Veamos dos pequeños ejemplos de uso Optional: con y sin nulo.
Optional<String> fullName = Optional.ofNullable( null );
System.out.println( "Full Name is set? " + fullName.isPresent() );
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) );
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
El método isPresent()devuelve verdadero si la instancia Optionalcontiene un valor no nulo y falso en caso contrario. El método orElseGet()contiene un mecanismo alternativo para el resultado si Optionalcontiene nulo, aceptando funciones para generar un valor predeterminado. El método map () transforma el valor actual Optionaly devuelve una nueva instancia Optional. El método orElse()es similar a orElseGet(), pero en lugar de una función, toma un valor predeterminado. Aquí está el resultado de este programa:
Full Name is set? false
Full Name: [none]
Hey Stranger!
Echemos un vistazo rápido a otro ejemplo:
Optional<String> firstName = Optional.of( "Tom" );
System.out.println( "First Name is set? " + firstName.isPresent() );
System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) );
System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
System.out.println();
El resultado será así:
First Name is set? true
First Name: Tom
Hey Tom!
Para obtener información más detallada, consulte la documentación oficial .

5.2. Corrientes

La API Stream recientemente agregada ( java.util.stream) introduce una programación de estilo funcional real en Java. Es, con diferencia, la incorporación más completa a la biblioteca de Java y permite a los desarrolladores de Java ser significativamente más eficientes y también les permite crear código eficiente, limpio y conciso. La API Stream facilita mucho el procesamiento de colecciones (pero no se limita a ellas, como veremos más adelante). Tomemos como ejemplo una clase simple Task.
public class Streams  {
    private enum Status {
        OPEN, CLOSED
    };

    private static final class Task {
        private final Status status;
        private final Integer points;

        Task( final Status status, final Integer points ) {
            this.status = status;
            this.points = points;
        }

        public Integer getPoints() {
            return points;
        }

        public Status getStatus() {
            return status;
        }

        @Override
        public String toString() {
            return String.format( "[%s, %d]", status, points );
        }
    }
}
La tarea tiene cierto sentido de puntos (o pseudodificultades) y puede estar ABIERTA o CERRADA . Presentemos una pequeña colección de problemas para jugar.
final Collection<Task> tasks = Arrays.asList(
    new Task( Status.OPEN, 5 ),
    new Task( Status.OPEN, 13 ),
    new Task( Status.CLOSED, 8 )
);
La primera pregunta que pretendemos averiguar es ¿cuántos puntos contienen actualmente las tareas ABIERTAS ? Antes de Java 8, la solución habitual para esto habría sido utilizar un iterador foreach. Pero en Java 8, la respuesta son los flujos: una secuencia de elementos que admiten operaciones agregadas secuenciales y paralelas.
// Подсчет общего количества очков всех активных задач с использованием sum()
final long totalPointsOfOpenTasks = tasks
    .stream()
    .filter( task -> task.getStatus() == Status.OPEN )
    .mapToInt( Task::getPoints )
    .sum();

System.out.println( "Total points: " + totalPointsOfOpenTasks );
Y la salida de la consola se verá así:
Total points: 18
Veamos lo que está pasando aquí. Primero, la colección de tareas se convierte en una representación de transmisión. Luego, la operación filterfiltra todas las tareas con un estado CERRADO . En el siguiente paso, la operación mapToIntconvierte los flujos Tasken flujos Integerutilizando un método Task::getPointspara cada instancia Task. Finalmente, todos los puntos se suman mediante el método sum, que proporciona el resultado final. Antes de pasar a los siguientes ejemplos, hay algunas notas sobre los hilos que se deben tener en cuenta (más detalles aquí ). Las operaciones streamse dividen en operaciones intermedias y finales . Las operaciones intermedias devuelven una nueva corriente. Siempre son vagos; cuando realizan operaciones intermedias como filter, en realidad no realizan filtrado, sino que crean una nueva secuencia que, cuando se completa, contiene los elementos de la secuencia original que coinciden con el predicado dado. Las operaciones finitas , como forEachy sum, se pueden pasar a través de una secuencia para producir un resultado o efecto secundario. Una vez que se completa la operación final, la transmisión se considera utilizada y no se puede volver a utilizar. En casi todos los casos, las operaciones finales tienden a completar su recorrido a través de la fuente de datos subyacente. Otra característica valiosa de los subprocesos es la compatibilidad con procesos paralelos listos para usar. Veamos este ejemplo, que encuentra la suma de las puntuaciones de todos los problemas.
// Calculate total points of all tasks
final double totalPoints = tasks
   .stream()
   .parallel()
   .map( task -> task.getPoints() ) // or map( Task::getPoints )
   .reduce( 0, Integer::sum );

System.out.println( "Total points (all tasks): " + totalPoints );
Esto es muy similar al primer ejemplo, excepto que intentamos procesar todas las tareas en paralelo y calcular el resultado final usando el método reduce. Aquí está la salida de la consola:
Total points (all tasks): 26.0
A menudo existe la necesidad de agrupar elementos según un criterio determinado. El ejemplo demuestra cómo los hilos pueden ayudar con esto.
// Группировка задач по их статусу
final Map<Status, List<Task>> map = tasks
    .stream()
    .collect( Collectors.groupingBy( Task::getStatus ) );
System.out.println( map );
La salida de la consola será la siguiente:
{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
Para terminar con los ejemplos de problemas, calculemos el porcentaje (o peso) general de cada problema en la colección en función de los puntos totales:
// Подсчет веса каждой задачи (Cómo процент от общего количества очков)
final Collection<String> result = tasks
    .stream()                                        // Stream<String>
    .mapToInt( Task::getPoints )                     // IntStream
    .asLongStream()                                  // LongStream
    .mapToDouble( points -> points / totalPoints )   // DoubleStream
    .boxed()                                         // Stream<Double>
    .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
    .mapToObj( percentage -> percentage + "%" )      // Stream<String>
    .collect( Collectors.toList() );                 // List<String>

System.out.println( result );
La salida de la consola será así:
[19%, 50%, 30%]
Finalmente, como señalamos anteriormente, Stream API no es solo para colecciones de Java. Una operación de E/S típica, como leer archivos de texto línea por línea, es una muy buena candidata para utilizar el procesamiento de flujo. He aquí un pequeño ejemplo para demostrarlo.
final Path path = new File( filename ).toPath();
try( Stream<String> lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {
    lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );
}
El método onConsole, que se llama en un subproceso, devuelve un subproceso equivalente con un controlador privado adicional. Se llama al controlador privado cuando close()se llama a un método en un hilo. La API Stream junto con las lambdas y los métodos de referencia junto con los métodos estáticos y predeterminados en Java 8 son la respuesta a los paradigmas modernos de desarrollo de software. Para obtener información más detallada, consulte la documentación oficial .

5.3. API de fecha/hora (JSR 310)

Java 8 aporta una nueva apariencia a la gestión de fecha y hora al proporcionar una nueva API de fecha y hora (JSR 310) . La manipulación de fecha y hora es uno de los peores puntos débiles para los desarrolladores de Java. El java.util.Dateseguimiento estándar java.util.Calendargeneralmente no mejoró la situación (tal vez incluso la hizo más confusa). Así nació Joda-Time : una excelente alternativa API de fecha/hora para Java . La nueva API de fecha/hora en Java 8 (JSR 310) está fuertemente influenciada por Joda-Time y aprovecha lo mejor de él. El nuevo paquete java.timecontiene todas las clases de fecha, hora, fecha/hora, zonas horarias, duraciones y manipulación de la hora . El diseño de la API tomó muy en serio la inmutabilidad: no se permiten cambios (una dura lección aprendida de java.util.Calendar). Si se requiere modificación, se devolverá una nueva instancia de la clase correspondiente. Veamos las clases principales y ejemplos de su uso. La primera clase Clock, que proporciona acceso al instante, fecha y hora actuales utilizando una zona horaria. Clockse puede utilizar en lugar de System.currentTimeMillis()y TimeZone.getDefault().
// Получить системное время Cómo смещение UTC
final Clock clock = Clock.systemUTC();
System.out.println( clock.instant() );
System.out.println( clock.millis() );
Ejemplo de salida de consola:
2014-04-12T15:19:29.282Z
1397315969360
Otras clases nuevas que veremos son LocaleDatey LocalTime. LocaleDatecontiene solo la parte de la fecha sin la zona horaria en el sistema de calendario ISO-8601. En consecuencia, LocalTimecontiene sólo una parte del código de tiempo>.
// получить местную fecha и время время
final LocalDate date = LocalDate.now();
final LocalDate dateFromClock = LocalDate.now( clock );

System.out.println( date );
System.out.println( dateFromClock );

// получить местную fecha и время время
final LocalTime time = LocalTime.now();
final LocalTime timeFromClock = LocalTime.now( clock );

System.out.println( time );
System.out.println( timeFromClock );
Ejemplo de salida de consola:
2014-04-12
2014-04-12
11:25:54.568
15:25:54.568
LocalDateTimeconcatena LocaleDatey LocalTimey contiene una fecha y hora, pero no una zona horaria, en el sistema de calendario ISO-8601. A continuación se ofrece un ejemplo sencillo.
// Get the local date/time
final LocalDateTime datetime = LocalDateTime.now();
final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );

System.out.println( datetime );
System.out.println( datetimeFromClock );
Ejemplo de salida de consola:
2014-04-12T11:37:52.309
2014-04-12T15:37:52.309
En caso de que necesite fecha/hora para una zona horaria específica, ZonedDateTime. Contiene la fecha y la hora en el sistema de calendario ISO-8601. A continuación se muestran algunos ejemplos para diferentes zonas horarias.
// Получение даты/времени для временной зоны
final ZonedDateTime zonedDatetime = ZonedDateTime.now();
final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );

System.out.println( zonedDatetime );
System.out.println( zonedDatetimeFromClock );
System.out.println( zonedDatetimeFromZone );
Ejemplo de salida de consola:
2014-04-12T11:47:01.017-04:00[America/New_York]
2014-04-12T15:47:01.017Z
2014-04-12T08:47:01.017-07:00[America/Los_Angeles]
Y finalmente, echemos un vistazo a la clase Duration: lapso de tiempo en segundos y nanosegundos. Esto hace que el cálculo entre dos fechas sea muy sencillo. Veamos cómo hacer esto:
// Получаем разницу между двумя датами
final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );
final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );

final Duration duration = Duration.between( from, to );
System.out.println( "Duration in days: " + duration.toDays() );
System.out.println( "Duration in hours: " + duration.toHours() );
El ejemplo anterior calcula la duración (en días y horas) entre dos fechas, 16 de abril de 2014 y 16 de abril de 2015 . A continuación se muestra un ejemplo de la salida de la consola:
Duration in days: 365
Duration in hours: 8783
La impresión general de la nueva fecha/hora en Java 8 es muy, muy positiva. En parte porque los cambios se basan en una base probada en batalla (Joda-Time), en parte porque esta vez el tema fue reconsiderado seriamente y se escucharon las voces de los desarrolladores. Para obtener más información, consulte la documentación oficial .

5.4. Motor JavaScript Nashorn

Java 8 viene con el nuevo motor JavaScript Nashorn , que le permite desarrollar y ejecutar ciertos tipos de aplicaciones JavaScript en la JVM. El motor JavaScript de Nashorn es simplemente otra implementación de javax.script.ScriptEngine que sigue el mismo conjunto de reglas para permitir que Java y JavaScript interactúen. He aquí un pequeño ejemplo.
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName( "JavaScript" );

System.out.println( engine.getClass().getName() );
System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );
Ejemplo de salida de consola:
jdk.nashorn.api.scripting.NashornScriptEngine
Result: 2

5.5. Base64

Finalmente, el soporte para la codificación Base64 llegó a la biblioteca estándar de Java con el lanzamiento de Java 8. Es muy fácil de usar, el ejemplo lo demuestra.
package com.javacodegeeks.java8.base64;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class Base64s {
    public static void main(String[] args) {
        final String text = "Base64 finally in Java 8!";

        final String encoded = Base64
            .getEncoder()
            .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
        System.out.println( encoded );

        final String decoded = new String(
            Base64.getDecoder().decode( encoded ),
            StandardCharsets.UTF_8 );
        System.out.println( decoded );
    }
}
La salida de la consola del programa muestra texto codificado y decodificado:
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!
También hay clases para codificadores/decodificadores compatibles con URL, así como codificadores/decodificadores compatibles con MIME ( Base64.getUrlEncoder()/ Base64.getUrlDecoder(), Base64.getMimeEncoder()/ Base64.getMimeDecoder()).

5.6. Matrices paralelas

La versión Java 8 agrega muchos métodos nuevos para el procesamiento de matrices en paralelo. Quizás el más importante de ellos sea parallelSort(), que puede acelerar enormemente la clasificación en máquinas multinúcleo. El pequeño ejemplo siguiente demuestra la nueva familia de métodos ( parallelXxx) en acción.
package com.javacodegeeks.java8.parallel.arrays;

import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;

public class ParallelArrays {
    public static void main( String[] args ) {
        long[] arrayOfLong = new long [ 20000 ];

        Arrays.parallelSetAll( arrayOfLong,
            index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
            i -> System.out.print( i + " " ) );
        System.out.println();

        Arrays.parallelSort( arrayOfLong );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
            i -> System.out.print( i + " " ) );
        System.out.println();
    }
}
Este pequeño fragmento de código utiliza un método parallelSetAll()para llenar una matriz con 20.000 valores aleatorios. Después de esto se aplica parallelSort(). El programa imprime los primeros 10 elementos antes y después de ordenar para mostrar que la matriz realmente está ordenada. La salida de un programa de ejemplo podría verse así (tenga en cuenta que los elementos de la matriz son aleatorios).
Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378
Sorted: 39 220 263 268 325 607 655 678 723 793

5.7. Paralelismo

Se han agregado nuevos métodos a la clase java.util.concurrent.ConcurrentHashMappara admitir operaciones agregadas basadas en los objetos de flujo y las expresiones lambda recién agregados. También se han agregado nuevos métodos a la clase java.util.concurrent.ForkJoinPoolpara admitir la agrupación compartida (consulte también nuestro curso gratuito sobre concurrencia de Java ). Se ha agregado una nueva clase java.util.concurrent.locks.StampedLockpara proporcionar bloqueo basado en capacidades con tres modos de acceso para control de lectura/escritura (puede considerarse como una mejor alternativa a la no tan buena java.util.concurrent.locks.ReadWriteLock). Nuevas clases que se han agregado al paquete java.util.concurrent.atomic:
  • Doble acumulador
  • Doble sumador
  • Acumulador largo
  • sumador largo

6. Nuevas funciones en el entorno de ejecución de Java (JVM)

El área PermGenha sido retirada y reemplazada por Metaspace (JEP 122). Las opciones de JVM -XX:PermSizey -XX:MaxPermSizehan sido reemplazadas por -XX:MetaSpaceSizey -XX:MaxMetaspaceSizerespectivamente.

7. Conclusión

El futuro está aquí: Java 8 ha hecho avanzar su plataforma al ofrecer funciones que permiten a los desarrolladores ser más productivos. Todavía es demasiado pronto para migrar los sistemas de producción a Java 8, pero la adopción debería comenzar a crecer lentamente en los próximos meses. Sin embargo, ahora es el momento de comenzar a preparar su código base para la compatibilidad con Java 8 y estar listo para incorporar los cambios de Java 8 cuando sea lo suficientemente seguro y estable. Como testimonio de la aceptación de Java 8 por parte de la comunidad, Pivotal lanzó recientemente Spring Framework con soporte de producción para Java 8 . Puede proporcionar su opinión sobre las nuevas e interesantes funciones de Java 8 en los comentarios.

8. Fuentes

Algunos recursos adicionales que analizan en profundidad varios aspectos de las características de Java 8:
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION