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