API para las que finalmente vale la pena actualizar desde Java 8. Parte 1

Java 8 es, con mucho, la versión más popular de Java y permanecerá con ella durante bastante tiempo. Sin embargo, cinco nuevas versiones de Java ya se han lanzado desde entonces (9, 10, 11, 12, 13), y pronto se lanzará otra Java 14. Una gran cantidad de nuevas características han aparecido en estas nuevas versiones. Por ejemplo, si cuenta en JEP, se implementaron 141 en total:



Sin embargo, en esta serie de artículos no habrá una lista seca de JEP. En cambio, solo quiero hablar sobre API interesantes que han aparecido en nuevas versiones. Cada artículo contendrá 10 API. En la elección y el orden de estas API no habrá ninguna lógica y regularidad específicas. Serán solo 10 API aleatorias, no TOP 10 y sin clasificar de la API más importante a la menos importante. Empecemos


1. Métodos Objects.requireNonNullElse() y Objects.requireNonNullElseGet()


Introducido en: Java 9


Comenzamos nuestra lista con dos métodos muy simples pero muy útiles en la clase java.util.Objects : requireNonNullElse() y requireNonNullElseGet() . Estos métodos le permiten devolver el objeto transmitido, si no es null , y si es null , devuelva el objeto por defecto. Por ejemplo:


 class MyCoder { private final Charset charset; MyCoder(Charset charset) { this.charset = Objects.requireNonNullElse( charset, StandardCharsets.UTF_8); } } 

requireNonNullElseGet() no es más que una versión perezosa de requireNonNullElse() . Puede ser útil si calcular el argumento predeterminado es costoso:


 class MyCoder { private final Charset charset; MyCoder(Charset charset) { this.charset = Objects.requireNonNullElseGet( charset, MyCoder::defaultCharset); } private static Charset defaultCharset() { // long operation... } } 

Sí, por supuesto, en ambos casos, uno podría prescindir fácilmente de estas funciones, por ejemplo, utilizando el operador ternario habitual u Optional , pero aún utilizando una función especial hace que el código sea un poco más corto y limpio. Y si usa importación y escritura estáticas, simplemente requireNonNullElse() lugar de Objects.requireNonNullElse() , entonces el código se puede reducir aún más.



2. Métodos de fábrica que devuelven colecciones inmutables


Introducido en: Java 9


Si los dos métodos anteriores son solo cosméticos, los métodos de fábrica de recolección estática realmente pueden reducir en gran medida el código e incluso mejorar su seguridad. Estos son los siguientes métodos introducidos en Java 9:



A la misma lista, puede agregar el Map.entry(K k, V v) que lo acompaña, que crea la Entry partir de la clave y el valor, así como los métodos para copiar colecciones que aparecieron en Java 10:



Los métodos de fábrica estáticos le permiten crear una colección inmutable e inicializarla en una sola acción:


 List<String> imageExtensions = List.of("bmp", "jpg", "png", "gif"); 

Si no utiliza bibliotecas de terceros, un código similar en Java 8 se verá mucho más engorroso:


 List<String> imageExtensions = Collections.unmodifiableList( Arrays.asList("bmp", "jpg", "png", "gif")); 

Y en el caso de Set o Map todavía es más triste, porque no hay análogos de Arrays.asList() para Set y Map .


Tal molestia provoca que muchas personas que escriben en Java 8 abandonen por completo las colecciones inmutables y usen siempre la ArrayList , HashSet y HashMap habituales, e incluso cuando se necesita el significado de colecciones inmutables. Como resultado, esto rompe el concepto de inmutable por defecto y reduce la seguridad del código.


Si finalmente actualiza desde Java 8, trabajar con colecciones inmutables se vuelve mucho más fácil y divertido gracias a los métodos de fábrica.



3. Files.readString() y Files.writeString()


Introducido en: Java 11


Java siempre ha sido conocido por su introducción pausada de métodos listos para operaciones frecuentes. Por ejemplo, para una de las operaciones más populares en la programación, leer un archivo, durante mucho tiempo no había un método listo. Solo 15 años después del lanzamiento de Java 1.0, apareció NIO, donde se introdujo el método Files.readAllBytes() para leer el archivo en una matriz de bytes.


Pero esto todavía no era suficiente, porque la gente a menudo tiene que trabajar con archivos de texto y para esto necesita leer cadenas del archivo, no bytes. Por lo tanto, en Java 8, se agregó el método Files.readAllLines() , devolviendo una List<String> .


Sin embargo, esto no fue suficiente, ya que la gente preguntó qué tan fácil era leer el archivo completo como una sola línea. Como resultado, para completar la imagen en Java 11, se Files.readString() el tan esperado método Files.readString() , cerrando así finalmente esta pregunta. Sorprendentemente, si un método similar estuvo presente en muchos otros idiomas desde el principio, entonces Java tardó más de 20 años en hacerlo.


Junto con readString() supuesto, también se introdujo el método simétrico writeString() . Estos métodos también tienen sobrecargas que le permiten especificar un Charset . Juntos, todo esto hace que trabajar con archivos de texto sea extremadamente conveniente. Un ejemplo:


 /**        */ private void reencodeFile(Path path, Charset from, Charset to) throws IOException { String content = Files.readString(path, from); Files.writeString(path, content, to); } 


4. Optional.ifPresentOrElse() y Optional.stream()


Introducido en: Java 9


Cuando Optional apareció en Java 8, no tenían una forma conveniente de realizar dos acciones diferentes, dependiendo de si tiene un valor o no. Como resultado, las personas tienen que recurrir a la cadena habitual isPresent() y get() :


 Optional<String> opt = ... if (opt.isPresent()) { log.info("Value = " + opt.get()); } else { log.error("Empty"); } 

O aún puedes esquivar de esta manera:


 Optional<String> opt = ... opt.ifPresent(str -> log.info("Value = " + str)); if (opt.isEmpty()) { log.error("Empty"); } 

Ambas opciones no son perfectas. Pero, comenzando con Java 9, esto se puede hacer de manera elegante usando el método Optional.ifPresentOrElse() :


 Optional<String> opt = ... opt.ifPresentOrElse( str -> log.info("Value = " + str), () -> log.error("Empty")); 

Otro nuevo método interesante en Java 9 es Optional.stream() , que devuelve un Stream de un elemento si el valor está presente, y un Stream vacío si no lo está. Tal método puede ser muy útil en cadenas con flatMap() . Por ejemplo, en este ejemplo, es muy simple obtener una lista de todos los números de teléfono de una empresa:


 class Employee { Optional<String> getPhoneNumber() { ... } } class Department { List<Employee> getEmployees() { ... } } class Company { List<Department> getDepartments() { ... } Set<String> getAllPhoneNumbers() { return getDepartments() .stream() .flatMap(d -> d.getEmployees().stream()) .flatMap(e -> e.getPhoneNumber().stream()) .collect(Collectors.toSet()); } } 

En Java 8, tendría que escribir algo como:


 e -> e.getPhoneNumber().map(Stream::of).orElse(Stream.empty()) 

Se ve voluminoso y no muy legible.



5. Process.pid() , Process.info() y ProcessHandle


Introducido en: Java 9


Si aún puede administrar sin las API anteriores, reemplazar el método Process.pid() en Java 8 será bastante problemático, especialmente multiplataforma. Este método devuelve el ID de proceso nativo:


 Process process = Runtime.getRuntime().exec("java -version"); System.out.println(process.pid()); 

Usando el método Process.info() , también puede encontrar información útil adicional sobre el proceso. Devuelve un objeto de tipo ProcessHandle.Info . Veamos qué nos devuelve para el proceso anterior:


 Process process = Runtime.getRuntime().exec("java -version"); ProcessHandle.Info info = process.info(); System.out.println("PID = " + process.pid()); System.out.println("User = " + info.user()); System.out.println("Command = " + info.command()); System.out.println("Args = " + info.arguments().map(Arrays::toString)); System.out.println("Command Line = " + info.commandLine()); System.out.println("Start Time = " + info.startInstant()); System.out.println("Total Time = " + info.totalCpuDuration()); 

Conclusión


 PID = 174 User = Optional[orionll] Command = Optional[/usr/lib/jvm/java-13-openjdk-amd64/bin/java] Args = Optional[[-version]] Command Line = Optional[/usr/lib/jvm/java-13-openjdk-amd64/bin/java -version] Start Time = Optional[2020-01-24T05:54:25.680Z] Total Time = Optional[PT0.01S] 

¿Qué sucede si el proceso no se inició desde el proceso Java actual? Para esto, ProcessHandle viene al ProcessHandle . Por ejemplo, obtengamos la misma información para el proceso actual usando el método ProcessHandle.current() :


 ProcessHandle handle = ProcessHandle.current(); ProcessHandle.Info info = handle.info(); System.out.println("PID = " + handle.pid()); System.out.println("User = " + info.user()); System.out.println("Command = " + info.command()); System.out.println("Args = " + info.arguments().map(Arrays::toString)); System.out.println("Command Line = " + info.commandLine()); System.out.println("Start Time = " + info.startInstant()); System.out.println("Total Time = " + info.totalCpuDuration()); 

Conclusión


 PID = 191 User = Optional[orionll] Command = Optional[/usr/lib/jvm/java-13-openjdk-amd64/bin/java] Args = Optional[[Main.java]] Command Line = Optional[/usr/lib/jvm/java-13-openjdk-amd64/bin/java Main.java] Start Time = Optional[2020-01-24T05:59:17.060Z] Total Time = Optional[PT1.56S] 

Para obtener un ProcessHandle para cualquier proceso mediante su PID, puede usar el método ProcessHandle.of() (devolverá Optional.empty si el proceso no existe).


También en ProcessHandle hay muchos otros métodos interesantes, por ejemplo, ProcessHandle.allProcesses() .



6. Métodos de String : isBlank() , strip() , stripLeading() , stripTrailing() , repeat() y lines()


Introducido en: Java 11


Toda una montaña de métodos útiles para cadenas apareció en Java 11.


El método String.isBlank() permite averiguar si una cadena consiste únicamente en espacios en blanco:


 System.out.println(" \n\r\t".isBlank()); // true 

Los String.stripLeading() , String.stripTrailing() y String.strip() eliminan los caracteres de espacio en blanco al principio de una línea, al final de una línea o en ambos extremos:


 String str = " \tHello, world!\t\n"; String str1 = str.stripLeading(); // "Hello, world!\t\n" String str2 = str.stripTrailing(); // " \tHello, world!" String str3 = str.strip(); // "Hello, world!" 

Tenga en cuenta que String.strip() no String.strip() lo mismo que String.trim() : el segundo solo elimina caracteres cuyo código es menor o igual que U + 0020, y el primero también elimina espacios de Unicode:


 System.out.println("str\u2000".strip()); // "str" System.out.println("str\u2000".trim()); // "str\u2000" 


El método String.repeat() concatena la cadena en sí n veces:


 System.out.print("Hello, world!\n".repeat(3)); 

Conclusión


 Hello, world! Hello, world! Hello, world! 

Finalmente, el método String.lines() divide la cadena en líneas. Goodbye String.split() , con el cual las personas constantemente confunden, qué argumento usar para él, ya sea "\n" o "\r" o "\n\r" (de hecho, es mejor usar regular expresión "\R" , que cubre todas las combinaciones). Además, String.lines() menudo puede ser más eficiente ya que devuelve líneas perezosamente.


 System.out.println("line1\nline2\nline3\n" .lines() .map(String::toUpperCase) .collect(Collectors.joining("\n"))); 

Conclusión


 LINE1 LINE2 LINE3 


7. String.indent()


Aparecido en: Java 12


Diluyamos nuestra historia con algo nuevo que apareció recientemente. Meet: el método String.indent() , que aumenta (o disminuye) la sangría de cada línea en una línea dada por el valor especificado. Por ejemplo:


 String body = "<h1>Title</h1>\n" + "<p>Hello, world!</p>"; System.out.println("<html>\n" + " <body>\n" + body.indent(4) + " </body>\n" + "</html>"); 

Conclusión


 <html> <body> <h1>Title</h1> <p>Hello, world!</p> </body> </html> 

Tenga en cuenta que para la última línea, String.indent() insertó el avance de línea, por lo que no tuvimos que agregar '\n' después de body.indent(4) .


Por supuesto, tal método será de gran interés en combinación con bloques de texto cuando se estabilicen, pero nada nos impide usarlo ahora sin ningún bloque de texto.



8. Métodos de takeWhile() : takeWhile() , dropWhile() , iterate() con un predicado y ofNullable()


Introducido en: Java 9


Stream.takeWhile() es similar a Stream.limit() , pero restringe Stream no por cantidad, sino por predicado. Tal necesidad de programación surge muy a menudo. Por ejemplo, si necesitamos obtener todas las entradas del diario para el año actual:


 [ { "date" : "2020-01-27", "text" : "..." }, { "date" : "2020-01-25", "text" : "..." }, { "date" : "2020-01-22", "text" : "..." }, { "date" : "2020-01-17", "text" : "..." }, { "date" : "2020-01-11", "text" : "..." }, { "date" : "2020-01-02", "text" : "..." }, { "date" : "2019-12-30", "text" : "..." }, { "date" : "2019-12-27", "text" : "..." }, ... ] 

Stream registros es casi infinita, por lo que filter() no se puede usar. Entonces takeWhile() viene al takeWhile() :


 getNotesStream() .takeWhile(note -> note.getDate().getYear() == 2020); 

Y si queremos obtener registros para 2019, entonces podemos usar dropWhile() :


 getNotesStream() .dropWhile(note -> note.getDate().getYear() == 2020) .takeWhile(note -> note.getDate().getYear() == 2019); 

En Java 8, Stream.iterate() solo podía generar un Stream infinito. Pero en Java 9, este método tiene una que toma un predicado. Gracias a esto, muchos bucles for ahora se pueden reemplazar con Stream :


 // Java 8 for (int i = 1; i < 100; i *= 2) { System.out.println(i); } 

 // Java 9+ IntStream .iterate(1, i -> i < 100, i -> i * 2) .forEach(System.out::println); 

Ambas versiones imprimen todos los grados de una deuce que no exceda de 100 :


 1 2 4 8 16 32 64 

Por cierto, el último código podría reescribirse usando takeWhile() :


 IntStream .iterate(1, i -> i * 2) .takeWhile(i -> i < 100) .forEach(System.out::println); 

Sin embargo, la opción con la iterate() tres argumentos iterate() sigue siendo más limpia (e IntelliJ IDEA sugiere corregirla nuevamente).


Finalmente, Stream.ofNullable() devuelve un Stream con un elemento si no es null , y un Stream vacío si es null . Este método es perfecto en el ejemplo anterior con teléfonos de la compañía si getPhoneNumber() devolverá una String anulable en lugar de Optional<String> :


 class Employee { String getPhoneNumber() { ... } } class Department { List<Employee> getEmployees() { ... } } class Company { List<Department> getDepartments() { ... } Set<String> getAllPhoneNumbers() { return getDepartments() .stream() .flatMap(d -> d.getEmployees().stream()) .flatMap(e -> Stream.ofNullable(e.getPhoneNumber())) .collect(Collectors.toSet()); } } 


9. Predicate.not()


Aparecido en: Java 11


Este método no introduce nada fundamentalmente nuevo y es más cosmético que fundamental. Sin embargo, la capacidad de acortar ligeramente el código siempre es muy agradable. Usando Predicate.not() lambdas que tienen negación se pueden reemplazar con referencias de métodos:


 Files.lines(path) .filter(str -> !str.isEmpty()) .forEach(System.out::println); 

Y ahora usando not() :


 Files.lines(path) .filter(not(String::isEmpty)) .forEach(System.out::println); 

Sí, los ahorros no son tan grandes, y si usa s -> !s.isEmpty() , la cantidad de caracteres, por el contrario, aumenta. Pero incluso en este caso, sigo prefiriendo la segunda opción, ya que es más declarativa y no utiliza una variable, lo que significa que el espacio de nombres no está abarrotado.



10. Limpiador


Aparecido en: Java 9


Quiero terminar la historia de hoy con una nueva API interesante que apareció en Java 9 y sirve para limpiar recursos antes de que el recolector de basura los elimine. Cleaner es un reemplazo seguro para el método Object.finalize() , que en sí mismo quedó obsoleto en Java 9.


Con Cleaner puede registrar una limpieza de recursos que sucederá si olvidó hacerlo explícitamente (por ejemplo, olvidó llamar al método close() o no usó try-with-resources ). Aquí hay un ejemplo de un recurso abstracto para el cual se registra una acción de limpieza en el constructor:


 public class Resource implements Closeable { private static final Cleaner CLEANER = Cleaner.create(); private final Cleaner.Cleanable cleanable; public Resource() { cleanable = CLEANER.register(this, () -> { //   // (,  ) }); } @Override public void close() { cleanable.clean(); } } 

En el buen sentido, los usuarios deberían crear dicho recurso en el bloque de try :


 try (var resource = new Resource()) { //   } 

Sin embargo, puede haber usuarios que se olviden de hacer esto y escriban simplemente var resource = new Resource() . En tales casos, la limpieza no se realizará de inmediato, sino que se llamará más adelante en uno de los siguientes ciclos de recolección de basura. Es mejor que nada.


Si desea estudiar Cleaner mejor y descubrir por qué nunca debe usar finalize() , le recomiendo que escuche mi charla sobre este tema.



Conclusión


Java no se detiene y se está desarrollando gradualmente. Mientras está sentado en Java 8, con cada versión hay cada vez más API nuevas e interesantes. Hoy revisamos 10 de estas API. Y puede usarlos todos si finalmente decide migrar desde Java 8.


La próxima vez veremos 10 API nuevas más.


Si no desea omitir la siguiente parte, le recomiendo que se suscriba a mi canal de Telegram , donde también publico noticias de Java.

Source: https://habr.com/ru/post/485750/


All Articles