Onze pérolas ocultas de Java 11

O Java 11 não apresentou nenhum recurso inovador, mas contém várias gemas das quais você talvez ainda não tenha ouvido falar. Já olhou o mais recente em String , Optional , Collection e outros cavalos de trabalho? Caso contrário, você chegou ao endereço: hoje veremos 11 jóias escondidas do Java 11!


Inferência de tipo para parâmetros lambda


Ao escrever uma expressão lambda, você pode escolher entre especificar tipos explicitamente e ignorá-los:


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

O Java 10 apresentou var , mas não pôde ser usado em lambdas:


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

No Java 11 já é possível. Mas porque? Não parece que var deu mais do que apenas um passe de tipo. Embora este seja o caso, o uso de var tem duas vantagens menores:


  • torna o uso de var mais universal, removendo a exceção à regra
  • permite adicionar anotações ao tipo de parâmetro sem recorrer ao uso de seu nome completo

Aqui está um exemplo do 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)) 

Embora a mistura de tipos derivados, explícitos e implícitos em expressões lambda da forma (var type, String option, index) -> ... possa ser implementada, mas ( na estrutura do JEP-323 ) este trabalho não foi realizado. Portanto, é necessário escolher uma das três abordagens e aderir a ela para todos os parâmetros da expressão lambda. A necessidade de especificar var para todos os parâmetros para adicionar anotação para um deles pode ser um pouco irritante, mas geralmente suportável.


Processamento de fluxo de strings com 'String::lines'


Tem uma sequência de várias linhas? Deseja fazer algo com cada linha? Então String::lines é a escolha certa:


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

Observe que a linha original usa os delimitadores \r\n Windows e, embora eu esteja no Linux, lines() ainda a quebrou. Isso se deve ao fato de que, apesar do sistema operacional atual, esse método interpreta \r , \n e \r\n como quebras de linha - mesmo se elas estiverem misturadas na mesma linha.


Um fluxo de linhas nunca contém os próprios separadores de linhas. As linhas podem estar vazias ( "\n\n \n\n" , que contém 5 linhas), mas a última linha da linha original será ignorada se estiver vazia ( "\n\n" ; 2 linhas). (Observação pelo tradutor: é conveniente que eles tenham line , mas que tenham string , e nós temos as duas.)


Diferente da split("\R") , as lines() preguiçosas e, cito , "fornecem melhor desempenho [...] procurando mais rapidamente novas quebras de linha". (Se alguém desejar registrar uma referência no JMH para verificação, informe-me). Também reflete melhor o algoritmo de processamento e usa uma estrutura de dados mais conveniente (fluxo em vez de matriz).


Removendo o espaço em branco com 'String::strip' etc.


Inicialmente, String tinha um método de remoção para remover espaços em branco, que era considerado tudo com códigos até U+0020 . Sim, BACKSPACE ( U+0008) é um espaço em branco como BELL ( U+0007 ), mas o LINE SEPARATOR ( U+2028 ) não é mais considerado como tal.


O Java 11 introduziu o método strip , cuja abordagem tem mais nuances. Ele usa o Character::isWhitespace do Java 5 para determinar o que exatamente precisa ser removido. A partir de sua documentação , é claro que isso:


  • SPACE SEPARATOR , SPACE SEPARATOR LINE SEPARATOR , LINE SEPARATOR PARAGRAPH SEPARATOR , mas não um espaço inextricável
  • 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 )

Com a mesma lógica, existem mais dois métodos de limpeza, stripLeading e stripTailing , que fazem exatamente o que é esperado deles.


E, finalmente, se você só precisa descobrir se a linha fica vazia depois de remover o espaço em branco, não há realmente necessidade de excluí-la - basta usar isBlank :


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

Repetindo strings com 'String::repeat'


Capte a ideia:


Etapa 1: Vigiando o JDK

Mantendo um olhar atento sobre o desenvolvimento do JDK


Etapa 2: Localizando perguntas relacionadas ao StackOverflow

Procurando perguntas relacionadas no Stackoverflow


Etapa 3: chegando com uma nova resposta com base em alterações futuras

Entre com uma nova resposta com base nas próximas alterações


Etapa 4: ????

Etapa 4: Lucro

¯ \ _ (ツ) _ / ¯


Como você pode imaginar, String possui um novo método repeat(int) . Funciona exatamente de acordo com as expectativas e há pouco a discutir.


Criando caminhos com 'Path::of'


Eu realmente gosto da API do Path , mas a conversão de caminhos entre diferentes modos de exibição (como Path , File , URL , URI e String ) ainda é irritante. Este ponto se tornou menos confuso no Java 11, copiando dois métodos Paths::get para Path::of methods:


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

Eles podem ser considerados canônicos, pois os dois métodos antigos Paths::get usam novas opções.


Lendo e gravando arquivos com 'Files::readString' e 'Files::writeString'


Se eu precisar ler de um arquivo grande, normalmente uso o Files::lines para obter um fluxo lento de suas linhas. Da mesma forma, para registrar uma grande quantidade de dados que não podem ser armazenados inteiramente na memória, eu uso o Files::write passando-os como Iterable<String> .


Mas e o caso simples quando quero processar o conteúdo de um arquivo como uma única linha? Isso não é muito conveniente, pois o Files::readAllBytes e as variantes apropriadas do Files::write operam em matrizes de bytes.


E, em seguida, o Java 11 aparece, adicionando readString e writeString aos Files :


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

Claro e fácil de usar. Se necessário, você pode passar o conjunto de readString para readString e, em writeString também uma matriz OpenOptions .


E / S vazia com 'Reader::nullReader' , etc.


Precisa de um OutputStream que não OutputStream lugar algum? Ou um InputStream vazio? E o Reader e o Writer que não fazem nada? O Java 11 tem tudo:


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

(Nota do tradutor: no commons-io do Apache, essas classes existem desde 2014).


No entanto, estou surpreso - é null realmente o melhor prefixo? Não gosto de como é usado para significar "ausência intencional" ... Talvez seja melhor usar noOp ? (Nota do tradutor: provavelmente esse prefixo foi escolhido devido ao uso comum de /dev/null .)


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


Como você converte coleções em matrizes?


 //  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()]); 

A primeira opção, objects , perde todas as informações sobre os tipos e, portanto, está em andamento. E o resto? Ambos são volumosos, mas o primeiro é mais curto. O último cria uma matriz do tamanho necessário, para que pareça mais produtivo (ou seja, "parece mais produtivo", veja credibilidade ). Mas é realmente mais produtivo? Não, pelo contrário, é mais lento (no momento).


Mas por que eu deveria me importar com isso? Não existe uma maneira melhor de fazer isso? No Java 11, existem:


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

Uma nova variante de Collection::toArray , que aceita IntFunction<T[]> , ou seja, uma função que recebe o tamanho da matriz e retorna uma matriz do tamanho necessário. Ele pode ser expresso brevemente como uma referência a um construtor do formato T[]::new (para um T bem conhecido).


Fato interessante: a implementação padrão da Collection#toArray(IntFunction<T[]>) sempre passa 0 para o gerador de array. Inicialmente, decidi que essa solução era baseada no melhor desempenho para matrizes de comprimento zero, mas agora acho que o motivo pode ser que, para algumas coleções, calcular o tamanho possa ser uma operação muito cara e você não deva usar essa abordagem na implementação padrão da Collection . No entanto, implementações de coleções específicas, como ArrayList , podem alterar essa abordagem, mas não são alteradas no Java 11. Não vale a pena, eu acho.


Verificação de ausência com 'Optional::isEmpty'


Com o uso abundante do Optional , especialmente em grandes projetos, onde você frequentemente encontra uma abordagem não Optional , é necessário verificar se ele tem um valor. Existe um método Optional::isPresent para isso. Mas com a mesma frequência, você precisa saber o oposto - esse Optional vazio. Não tem problema, basta usar !opt.isPresent() , certo?


Obviamente, é possível assim, mas quase sempre é mais fácil entender a lógica if sua condição não for invertida. E, às vezes, o Optional aparece no final de uma longa cadeia de chamadas e, se você precisar checar por nada, então precisa apostar ! no começo:


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

Nesse caso, pule ! muito facil A partir do Java 11, há uma opção melhor:


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

Invertendo predicados com 'Predicate::not'


Falando em inverter ... A interface do Predicate possui um negate instância negate : retorna um novo predicado que executa a mesma verificação, mas inverte o resultado. Infelizmente, eu raramente consigo usá-lo ...


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

O problema é que raramente tenho acesso à instância do Predicate . Mais frequentemente, quero obter essa instância por meio de um link para um método (e invertê-lo), mas, para que isso funcione, o compilador deve saber o que levar a referência ao método - sem ele, ele não pode fazer nada. E é exatamente isso que acontece se você usar a construção (String::isBlank).negate() : o compilador não sabe mais o que String::isBlank deve estar nisso e desiste. Uma casta especificada corretamente corrige isso, mas a que custo?


Embora exista uma solução simples. Não use o negate instância negate , mas use o novo método estático Predicate.not(Predicate<T>) do Java 11:


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

Já está melhor!


Expressões regulares como um predicado com 'Pattern::asMatchPredicate'


Existe uma expressão regular? Precisa filtrar dados nele? Que tal isso:


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

Fiquei muito feliz em encontrar esse método! Vale a pena acrescentar que este é um método do Java 8. Opa, eu perdi então. O Java 11 adicionou outro método semelhante: Pattern::asMatchPredicate . Qual a diferença?


  • asPredicate verifica se a sequência ou parte da sequência corresponde ao padrão (funciona como s -> this.matcher(s).find() )
  • asMatchPredicate verifica se a sequência inteira corresponde ao padrão (funciona como s -> this.matcher(s).matches() )

Por exemplo, temos uma expressão regular que verifica os números de telefone, mas não contém ^ e $ para rastrear o início e o fim de uma linha. Em seguida, o código a seguir não funcionará conforme o esperado:


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

Você percebeu um erro? Uma linha como " -152 ? +1-202-456-1414" será filtrada porque contém um número de telefone válido. Por outro lado, Pattern::asMatchPredicate não permitirá isso, porque a cadeia inteira não corresponderá mais ao padrão.


Auto-teste


Aqui está uma visão geral de todas as onze pérolas - você ainda se lembra do que cada método faz? Nesse caso, você passou no teste.


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

Divirta-se com o Java 11!

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


All Articles