APIs para as quais finalmente vale a pena atualizar do Java 8. Parte 1

O Java 8 é de longe a versão mais popular do Java e permanecerá com ele por algum tempo. No entanto, cinco novas versões do Java já foram lançadas desde então (9, 10, 11, 12, 13), e em breve outro Java 14 será lançado. Um grande número de novos recursos apareceu nessas novas versões. Por exemplo, se você contar nos JEPs, 141 no total foram implementados:



No entanto, nesta série de artigos, não haverá uma lista seca de PEC. Em vez disso, só quero falar sobre APIs interessantes que apareceram em novas versões. Cada artigo conterá 10 APIs. Na escolha e ordem dessas APIs, não haverá lógica e regularidade específicas. Serão apenas 10 APIs aleatórias, não TOP 10 e sem classificação da API mais importante para a menos importante. Vamos começar.


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


Introduzido em: Java 9


Começamos nossa lista com dois métodos muito simples, mas muito úteis, na classe java.util.Objects : requireNonNullElse() e requireNonNullElseGet() . Esses métodos permitem retornar o objeto transmitido, se não for null e, se for null , retorne o objeto por padrão. Por exemplo:


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

requireNonNullElseGet() nada mais é do que uma versão lenta do requireNonNullElse() . Pode ser útil se o cálculo do argumento padrão for caro:


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

Sim, é claro, em ambos os casos, é possível facilmente passar sem essas funções, por exemplo, usando o operador ternário usual ou Optional , mas ainda assim usar uma função especial torna o código um pouco mais curto e mais limpo. E se você usar a importação estática e a gravação, basta requireNonNullElse() vez de Objects.requireNonNullElse() , o código poderá ser reduzido ainda mais.



2. Métodos de fábrica retornando coleções imutáveis


Introduzido em: Java 9


Se os dois métodos anteriores forem apenas cosméticos, os métodos de fábrica de coleta estática podem realmente reduzir bastante o código e até melhorar sua segurança. Estes são os seguintes métodos introduzidos no Java 9:



Na mesma lista, você pode adicionar o Map.entry(K k, V v) que acompanha, que cria Entry partir da chave e do valor, bem como métodos para copiar coleções que apareceram no Java 10:



Os métodos estáticos de fábrica permitem criar uma coleção imutável e inicializá-la em uma ação:


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

Se você não usar bibliotecas de terceiros, código semelhante no Java 8 parecerá muito mais complicado:


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

E no caso de Set ou Map ainda é mais triste, porque não há análogos de Arrays.asList() para Set e Map .


Essa complexidade leva muitas pessoas que escrevem no Java 8 a abandonar completamente as coleções imutáveis ​​e sempre usam o habitual ArrayList , HashSet e HashMap , e mesmo onde o significado de coleções imutáveis ​​é necessário. Como resultado, isso quebra o conceito de imutável por padrão e reduz a segurança do código.


Se você finalmente atualizar do Java 8, o trabalho com coleções imutáveis ​​se tornará muito mais fácil e agradável, graças aos métodos de fábrica.



3. Files.readString() e Files.writeString()


Introduzido em: Java 11


O Java sempre foi conhecido por sua introdução lenta de métodos prontos para operações frequentes. Por exemplo, para uma das operações mais populares em programação, a leitura de um arquivo, por muito tempo, não havia método pronto. Apenas 15 anos após o lançamento do Java 1.0, o NIO apareceu, onde o método Files.readAllBytes() foi introduzido para ler o arquivo em uma matriz de bytes.


Mas isso ainda não foi suficiente, porque as pessoas geralmente precisam trabalhar com arquivos de texto e, para isso, é necessário ler as strings do arquivo, não bytes. Portanto, no Java 8, o método Files.readAllLines() foi adicionado, retornando uma List<String> .


No entanto, isso não foi suficiente, pois as pessoas perguntaram como era fácil ler o arquivo inteiro como uma única linha. Como resultado, para concluir a imagem no Java 11, o tão esperado método Files.readString() foi Files.readString() , encerrando finalmente esta questão. Surpreendentemente, se um método semelhante estava presente em muitas outras linguagens desde o início, o Java levou mais de 20 anos para fazer isso.


Juntamente com readString() claro, o método writeString() simétrico também foi introduzido. Esses métodos também possuem sobrecargas que permitem especificar um conjunto de caracteres. Juntos, tudo isso torna o trabalho com arquivos de texto extremamente conveniente. Um exemplo:


 /**        */ 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() e Optional.stream()


Introduzido em: Java 9


Quando o Optional apareceu no Java 8, eles não possuíam uma maneira conveniente de executar duas ações diferentes, dependendo se ele possui ou não um valor. Como resultado, as pessoas precisam recorrer à cadeia usual isPresent() e get() :


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

Ou você ainda pode se esquivar dessa maneira:


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

Ambas as opções não são perfeitas. Mas, começando no Java 9, isso pode ser feito de maneira elegante usando o método Optional.ifPresentOrElse() :


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

Outro novo método interessante no Java 9 é o Optional.stream() , que retorna um Stream de um elemento se o valor estiver presente e um Stream vazio, se não estiver. Esse método pode ser muito útil em cadeias com flatMap() . Por exemplo, neste exemplo, é muito simples obter uma lista de todos os números de telefone de uma 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()); } } 

No Java 8, você teria que escrever algo como:


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

Parece volumoso e não muito legível.



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


Introduzido em: Java 9


Se você ainda puder gerenciar sem as APIs anteriores, a substituição do método Process.pid() no Java 8 será bastante problemática, especialmente entre plataformas. Este método retorna o ID do processo nativo:


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

Usando o método Process.info() , você também pode encontrar informações úteis adicionais sobre o processo. Retorna um objeto do tipo ProcessHandle.Info . Vamos ver o que ele nos retorna para o processo acima:


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

Conclusão:


 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] 

E se o processo não foi iniciado a partir do atual processo Java? Para isso, ProcessHandle vem em ProcessHandle . Por exemplo, vamos obter todas as mesmas informações para o processo atual usando o 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()); 

Conclusão:


 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 obter um ProcessHandle para qualquer processo por seu PID, você pode usar o método ProcessHandle.of() (ele retornará Optional.empty se o processo não existir).


Também no ProcessHandle existem muitos outros métodos interessantes, por exemplo, ProcessHandle.allProcesses() .



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


Introduzido em: Java 11


Uma montanha inteira de métodos úteis para strings apareceu no Java 11.


O método String.isBlank() permite descobrir se uma sequência consiste apenas em espaço em branco:


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

Os String.stripLeading() , String.stripTrailing() e String.strip() removem caracteres de espaço em branco no início de uma linha, no final de uma linha ou nas duas extremidades:


 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!" 

Observe que String.strip() não String.strip() o mesmo que String.trim() : o segundo remove apenas caracteres cujo código é menor ou igual a U + 0020 e o primeiro também remove espaços do Unicode:


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


O método String.repeat() concatena a própria sequência n vezes:


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

Conclusão:


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

Finalmente, o método String.lines() divide a string em linhas. Adeus String.split() , com a qual as pessoas confundem constantemente, qual argumento usar para isso, "\n" ou "\r" ou "\n\r" (na verdade, é melhor usar regularmente expressão "\R" , que abrange todas as combinações). Além disso, String.lines() geralmente pode ser mais eficiente, pois retorna linhas lentamente.


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

Conclusão:


 LINE1 LINE2 LINE3 


7. String.indent()


Apareceu em: Java 12


Vamos diluir nossa história com algo novo que apareceu recentemente. Meet: o método String.indent() , que aumenta (ou diminui) a indentação de cada linha em uma determinada linha pelo valor especificado. Por exemplo:


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

Conclusão:


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

Observe que, para a última linha, o próprio String.indent() inseriu o feed da linha, portanto não precisamos adicionar '\n' após o body.indent(4) .


Obviamente, esse método será de maior interesse em combinação com blocos de texto quando eles se tornarem estáveis, mas nada nos impede de usá-lo agora sem nenhum bloco de texto.



8. Métodos de Stream : takeWhile() , dropWhile() , iterate() com um predicado e ofNullable()


Introduzido em: Java 9


Stream.takeWhile() é semelhante a Stream.limit() , mas restringe o Stream não por quantidade, mas por predicado. Essa necessidade de programação surge com muita frequência. Por exemplo, se precisarmos obter todas as entradas do diário para o ano atual:


 [ { "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 é quase infinito; portanto, filter() não pode ser usado. Então takeWhile() vem ao takeWhile() :


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

E se queremos obter registros para 2019, podemos usar dropWhile() :


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

No Java 8, Stream.iterate() poderia gerar apenas um Stream infinito. Mas no Java 9, esse método possui uma que requer um predicado. Graças a isso, muitos loops agora podem ser substituídos pelo 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 as versões imprimem todos os graus de empate que não excedem 100 :


 1 2 4 8 16 32 64 

A propósito, o último código pode ser reescrito usando takeWhile() :


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

No entanto, a opção com a iterate() três argumentos iterate() ainda é mais limpa (e o IntelliJ IDEA sugere corrigi-la novamente).


Por fim, Stream.ofNullable() retorna um Stream com um elemento se não for null e um Stream vazio se for null . Este método é perfeito no exemplo acima com os telefones da empresa, se getPhoneNumber() retornar uma String anulável em vez 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()


Apareceu em: Java 11


Este método não introduz nada fundamentalmente novo e é mais cosmético do que fundamental. No entanto, a capacidade de encurtar um pouco o código é sempre muito agradável. Usando Predicate.not() lambdas com negação podem ser substituídas por referências de método:


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

E agora usando not() :


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

Sim, a economia não é tão grande e, se você usar s -> !s.isEmpty() , o número de caracteres, pelo contrário, se tornará maior. Mas, mesmo neste caso, ainda prefiro a segunda opção, pois é mais declarativa e não usa uma variável nela, o que significa que o espaço para nome não está confuso.



10. Limpador


Apareceu em: Java 9


Quero encerrar a história de hoje com uma nova API interessante que apareceu no Java 9 e serve para limpar os recursos antes que eles sejam descartados pelo coletor de lixo. Cleaner é um substituto seguro para o método Object.finalize() , que foi descontinuado no Java 9.


Usando o Cleaner você pode registrar uma limpeza de recursos que ocorrerá se você se esquecer de fazê-lo explicitamente (por exemplo, se esqueceu de chamar o método close() ou se não usou o try-with-resources ). Aqui está um exemplo de um recurso abstrato para o qual uma ação de limpeza é registrada no construtor:


 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(); } } 

De uma maneira boa, os usuários devem criar esse recurso no bloco try :


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

No entanto, pode haver usuários que se esquecem de fazer isso e escrevem simplesmente var resource = new Resource() . Nesses casos, a limpeza não será realizada imediatamente, mas será chamada posteriormente em um dos seguintes ciclos de coleta de lixo. É melhor que nada.


Se você deseja estudar melhor o Cleaner e descobrir por que você nunca deve usar finalize() , recomendo que você ouça minha palestra sobre este tópico.



Conclusão


Java não fica parado e está se desenvolvendo gradualmente. Enquanto você está no Java 8, em cada versão, há mais e mais novas APIs interessantes. Hoje analisamos 10 dessas APIs. E você pode usá-los todos se finalmente decidir migrar do Java 8.


Da próxima vez, veremos mais 10 novas APIs.


Se você não quiser pular a próxima parte, recomendo que você assine meu canal Telegram , onde também publico notícias sobre Java.

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


All Articles