Once perlas ocultas de Java 11

Java 11 no introdujo ninguna característica innovadora, pero contiene varias gemas de las que quizás aún no haya oído hablar. ¿Ya viste lo último en String , Optional , Collection y otros caballos de batalla? Si no, ha llegado a la dirección: ¡hoy veremos 11 gemas ocultas de Java 11!


Inferencia de tipos para parámetros lambda


Al escribir una expresión lambda, puede elegir entre especificar explícitamente los tipos y omitirlos:


 Function<String, String> append = string -> string + " "; Function<String, String> append = (String s) -> s + " "; 

Java 10 introdujo var , pero no se pudo usar en lambdas:


 //    Java 10 Function<String, String> append = (var string) -> string + " "; 

En Java 11 ya es posible. Pero por que? No parece que var dado más que un simple pase de tipo. Aunque este es el caso, el uso de var tiene dos ventajas menores:


  • hace que el uso de var más universal al eliminar la excepción a la regla
  • le permite agregar anotaciones al tipo de parámetro sin recurrir al uso de su nombre completo

Aquí hay un ejemplo del segundo caso:


 List<EnterpriseGradeType<With, Generics>> types = /*...*/; types .stream() // ,     @Nonnull   .filter(type -> check(type)) //  Java 10    ~>  .filter((@Nonnull EnterpriseGradeType<With, Generics> type) -> check(type)) //  Java 11    ~>   .filter((@Nonnull var type) -> check(type)) 

Aunque se puede implementar la mezcla de tipos derivados, explícitos e implícitos en expresiones lambda de la forma (var type, String option, index) -> ... , pero ( en el marco de JEP-323 ) este trabajo no se llevó a cabo. Por lo tanto, es necesario elegir uno de los tres enfoques y adherirse a él para todos los parámetros de la expresión lambda. La necesidad de especificar var para todos los parámetros para agregar anotaciones para uno de ellos puede ser un poco molesto, pero generalmente tolerable.


Procesamiento continuo de cadenas con 'String::lines'


¿Tienes una cadena de varias líneas? ¿Quieres hacer algo con cada línea? Entonces String::lines es la elección correcta:


 var multiline = "\r\n\r\n\r\n"; multiline .lines() //Stream<String> .map(line -> "// " + line) .forEach(System.out::println); // : //  //  //  //  

Tenga en cuenta que la línea original usa los delimitadores de tornillo \r\n y aunque estoy en Linux, las lines() todavía lo rompieron. Esto se debe al hecho de que, a pesar del sistema operativo actual, este método interpreta \r , \n y \r\n como saltos de línea, incluso si se mezclan en la misma línea.


Una secuencia de líneas nunca contiene los propios separadores de línea. Las líneas pueden estar vacías ( "\n\n \n\n" , que contiene 5 líneas), pero la última línea de la línea original se ignora si está vacía ( "\n\n" ; 2 líneas). (Nota del traductor: es conveniente para ellos tener line , pero tener string , y tenemos ambos).


A diferencia de la split("\R") , las lines() vagas y, cito , "proporciona un mejor rendimiento [...] al buscar más rápidamente nuevos saltos de línea". (Si alguien quiere presentar un punto de referencia en JMH para verificación, hágamelo saber). También refleja mejor el algoritmo de procesamiento y utiliza una estructura de datos más conveniente (flujo en lugar de matriz).


Eliminando espacios en blanco con 'String::strip' , etc.


Inicialmente, String tenía un método de trim para eliminar espacios en blanco, que se consideraba todo con códigos hasta U+0020 . Sí, BACKSPACE ( U+0008) es un espacio en blanco como BELL ( U+0007 ), pero LINE SEPARATOR ( U+2028 ) ya no se considera como tal.


Java 11 introdujo el método de la strip , cuyo enfoque tiene más matices. Utiliza el Character::isWhitespace de Java 5 para determinar qué necesita eliminarse exactamente. De su documentación queda claro que esto:


  • SPACE SEPARATOR LINE SEPARATOR SPACE SEPARATOR , LINE SEPARATOR , LINE SEPARATOR PARAGRAPH SEPARATOR , pero no es un espacio inextricable
  • HORIZONTAL TABULATION ( U+0009 ), LINE FEED ( U+000A ), VERTICAL TABULATION ( U+000B ), FORM FEED ( U+000C ), CARRIAGE RETURN ( U+000D )
  • FILE SEPARATOR ( U+001C ), GROUP SEPARATOR ( U+001D ), RECORD SEPARATOR ( U+001E ), UNIT SEPARATOR ( U+001F )

Con la misma lógica, hay dos métodos de limpieza más, stripLeading y stripTailing , que hacen exactamente lo que se espera de ellos.


Y finalmente, si solo necesita averiguar si la línea se vacía después de eliminar el espacio en blanco, entonces no hay necesidad de eliminarlos realmente, solo use isBlank :


 " ".isBlank(); //  ~> true " ".isBlank(); //   ~> false 

Repetir cadenas con 'String::repeat'


Captura la idea:


Paso 1: Vigilar JDK

Vigilando de cerca el desarrollo de JDK


Paso 2: Búsqueda de preguntas relacionadas con StackOverflow

Buscando preguntas relacionadas en Stackoverflow


Paso 3: llegar con una nueva respuesta basada en cambios futuros

Swoop in con nueva respuesta basada en los próximos cambios


Paso 4: ????

Paso 4: ganancia

¯ \ _ (ツ) _ / ¯


Como puedes imaginar, String tiene un nuevo método de repeat(int) . Funciona exactamente de acuerdo con las expectativas, y hay poco que discutir.


Crear rutas con 'Path::of'


Realmente me gusta la API de Path , pero convertir rutas entre diferentes vistas (como Path , File , URL , URI y String ) sigue siendo molesto. Este punto se ha vuelto menos confuso en Java 11 copiando dos Paths::get method en Path::of method:


 Path tmp = Path.of("/home/nipa", "tmp"); Path codefx = Path.of(URI.create("http://codefx.org")); 

Pueden considerarse canónicos, ya que los dos métodos antiguos de Paths::get utilizan nuevas opciones.


Lectura y escritura de archivos con 'Files::readString' y 'Files::writeString'


Si necesito leer de un archivo grande, generalmente uso Files::lines para obtener un flujo lento de sus líneas. Del mismo modo, para escribir una gran cantidad de datos que pueden no almacenarse en la memoria en su totalidad, utilizo Files::write pasándolos como Iterable<String> .


Pero, ¿qué pasa con el caso simple cuando quiero procesar el contenido de un archivo como una sola línea? Esto no es muy conveniente, ya que Files::readAllBytes y las variantes apropiadas de Files::write operan en conjuntos de bytes.


Y luego aparece Java 11, agregando readString y writeString a los Files :


 String haiku = Files.readString(Path.of("haiku.txt")); String modified = modify(haiku); Files.writeString(Path.of("haiku-mod.txt"), modified); 

Claro y fácil de usar. Si es necesario, puede pasar el Charset de readString a readString , y en writeString también una matriz OpenOptions .


E / S vacía con 'Reader::nullReader' , etc.


¿Necesita un OutputStream que no escriba en ningún lado? ¿O un InputStream vacío? ¿Qué pasa con Reader y Writer que no hacen nada? Java 11 lo tiene todo:


 InputStream input = InputStream.nullInputStream(); OutputStream output = OutputStream.nullOutputStream(); Reader reader = Reader.nullReader(); Writer writer = Writer.nullWriter(); 

(Nota del traductor: en commons-io estas clases existen desde aproximadamente 2014).


Sin embargo, me sorprende: ¿es null realmente el mejor prefijo? No me gusta cómo se usa para significar "ausencia intencional" ... ¿Quizás sería mejor usar noOp ? (Nota del traductor: lo más probable es que este prefijo se haya elegido debido al uso común de /dev/null ).


{ } ~> [ ] con 'Collection::toArray'


¿Cómo se convierten las colecciones en matrices?


 //  Java 11 List<String> list = /*...*/; Object[] objects = list.toArray(); String[] strings_0 = list.toArray(new String[0]); String[] strings_size = list.toArray(new String[list.size()]); 

La primera opción, objects , pierde toda la información sobre los tipos, por lo que está en fuga. ¿Qué hay del resto? Ambos son voluminosos, pero el primero es más corto. Este último crea una matriz del tamaño requerido, para que se vea más productivo (es decir, "parece más productivo", ver credibilidad ). ¿Pero es realmente más productivo? No, por el contrario, es más lento (por el momento).


Pero, ¿por qué debería importarme esto? ¿No hay una mejor manera de hacer esto? En Java 11 hay:


 String[] strings_fun = list.toArray(String[]::new); 

Collection::toArray una nueva variante de Collection::toArray , que acepta IntFunction<T[]> , es decir una función que recibe el tamaño de la matriz y devuelve una matriz del tamaño requerido. Se puede expresar brevemente como una referencia a un constructor de la forma T[]::new (para un T conocido).


Dato interesante, la implementación predeterminada de Collection#toArray(IntFunction<T[]>) siempre pasa 0 al generador de matriz. Al principio, decidí que esta solución se basaba en el mejor rendimiento para matrices de longitud cero, pero ahora creo que la razón puede ser que para algunas colecciones, calcular el tamaño puede ser una operación muy costosa y no debe usar este enfoque en la implementación predeterminada de Collection . Sin embargo, las implementaciones de colecciones específicas, como ArrayList , pueden cambiar este enfoque, pero no cambian en Java 11. No vale la pena, supongo.


Verificación de ausencia con 'Optional::isEmpty'


Con el uso abundante de Optional , especialmente en proyectos grandes, donde a menudo encuentra un enfoque no Optional , a menudo tiene que verificar si tiene un valor. Hay un método Optional::isPresent para esto. Pero con la misma frecuencia necesita saber lo contrario: que Optional vacío. No hay problema, solo use !opt.isPresent() , ¿verdad?


Por supuesto, es posible de esa manera, pero casi siempre es más fácil entender la lógica if su condición no se invierte. Y a veces, Optional aparece al final de una larga cadena de llamadas y si necesita verificarlo para nada, ¡entonces tiene que apostar ! al principio


 public boolean needsToCompleteAddress(User user) { return !getAddressRepository() .findAddressFor(user) .map(this::canonicalize) .filter(Address::isComplete) .isPresent(); } 

En ese caso, ¡sáltatelo ! Muy fácil Comenzando con Java 11 hay una mejor opción:


 public boolean needsToCompleteAddress(User user) { return getAddressRepository() .findAddressFor(user) .map(this::canonicalize) .filter(Address::isComplete) .isEmpty(); } 

Invertir predicados con 'Predicate::not'


Hablando de invertir ... La interfaz de Predicate tiene un negate instancia negate : devuelve un nuevo predicado que realiza la misma verificación, pero invierte su resultado. Desafortunadamente, rara vez logro usarlo ...


 //      Stream .of("a", "b", "", "c") // ,  ~>        .filter(s -> !s.isBlank()) //          ~>  .filter((String::isBlank).negate()) // ,  ~>       .filter(((Predicate<String>) String::isBlank).negate()) .forEach(System.out::println); 

El problema es que rara vez tengo acceso a la instancia de Predicate . Más a menudo, quiero obtener una instancia de este tipo a través de un enlace a un método (e invertirlo), pero para que esto funcione, el compilador debe saber a qué llevar la referencia del método; sin él, no puede hacer nada. Y esto es exactamente lo que sucede si usa la (String::isBlank).negate() : el compilador ya no sabe qué String::isBlank debería estar en esto y se da por vencido. Una casta correctamente especificada arregla esto, pero ¿a qué costo?


Aunque hay una solución simple. No use el negate instancia negate , pero use el nuevo método estático Predicate.not(Predicate<T>) de Java 11:


 Stream .of("a", "b", "", "c") //   `java.util.function.Predicate.not` .filter(not(String::isBlank)) .forEach(System.out::println); 

Ya mejor!


Expresiones regulares como predicado con 'Pattern::asMatchPredicate'


¿Hay una expresión regular? ¿Necesita filtrar datos en él? ¿Qué tal esto?


 Pattern nonWordCharacter = Pattern.compile("\\W"); Stream .of("Metallica", "Motörhead") .filter(nonWordCharacter.asPredicate()) .forEach(System.out::println); 

¡Estaba muy feliz de encontrar este método! Vale la pena agregar que este es un método de Java 8. Vaya, lo perdí entonces. Java 11 agregó otro método similar: Pattern::asMatchPredicate . Cual es la diferencia


  • asPredicate comprueba que la cadena o parte de la cadena coincida con el patrón (funciona como s -> this.matcher(s).find() )
  • asMatchPredicate verifica que toda la cadena coincida con el patrón (funciona como s -> this.matcher(s).matches() )

Por ejemplo, tenemos una expresión regular que verifica los números de teléfono, pero no contiene ^ y $ para rastrear el principio y el final de una línea. Entonces el siguiente código no funcionará como es de esperar:


 prospectivePhoneNumbers .stream() .filter(phoneNumberPatter.asPredicate()) .forEach(this::robocall); 

¿Notaste un error? Se " -152 ? +1-202-456-1414" una línea como " -152 ? +1-202-456-1414" , porque contiene un número de teléfono válido. Por otro lado, Pattern::asMatchPredicate no permitirá esto, porque toda la cadena ya no coincidirá con el patrón.


Auto prueba


Aquí hay una descripción general de las once perlas: ¿todavía recuerda lo que hace cada método? Si es así, has pasado la prueba.


  • en String :
    • Stream<String> lines()
    • String strip()
    • String stripLeading()
    • String stripTrailing()
    • boolean isBlank()
    • String repeat(int)
  • en el Path :
    • static Path of(String, String...)
    • static Path of(URI)
  • en Files :
    • String readString(Path) throws IOException
    • Path writeString(Path, CharSequence, OpenOption...) throws IOException
    • Path writeString(Path, CharSequence, Charset, OpenOption...) throws IOException
  • en InputStream : static InputStream nullInputStream()
  • en OutputStream : static OutputStream nullOutputStream()
  • en el Reader : Reader static Reader nullReader()
  • en Writer : Writer static Writer nullWriter()
  • en la Collection : T[] toArray(IntFunction<T[]>)
  • en Optional : boolean isEmpty()
  • en Predicate : Predicate static Predicate<T> not(Predicate<T>)
  • en Pattern : Predicate<String> asMatchPredicate()

¡Diviértete con Java 11!

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


All Articles