JavaRush /Blogue Java /Random-PT /Pausa para café #155. As 10 principais funções em Java

Pausa para café #155. As 10 principais funções em Java

Publicado no grupo Random-PT

As 10 principais funções em Java

Fonte: DZone Este artigo lista dez recursos de programação Java que são frequentemente usados ​​por desenvolvedores em seu trabalho diário. Pausa para café #155.  As 10 principais funções em Java - 1

1. Método de fábrica de coleta

As coleções são um dos recursos mais comumente usados ​​na programação. Eles são usados ​​como um recipiente no qual armazenamos objetos e os repassamos. As coleções também são usadas para classificar, pesquisar e repetir objetos, facilitando muito a vida do programador. Eles possuem diversas interfaces básicas como List, Set, Map, além de diversas implementações. A forma tradicional de criar Coleções e Mapas pode parecer prolixa para muitos desenvolvedores. É por isso que o Java 9 introduziu vários métodos de fábrica concisos. Lista :
List countries = List.of("Bangladesh", "Canada", "United States", "Tuvalu");
Definir :
Set countries = Set.of("Bangladesh", "Canada", "United States", "Tuvalu");
Mapa :
Map countriesByPopulation = Map.of("Bangladesh", 164_689_383,
                                                            "Canada", 37_742_154,
                                                            "United States", 331_002_651,
                                                            "Tuvalu", 11_792);
O método factory é muito útil quando queremos criar containers imutáveis. Mas se você for criar coleções mutáveis, é recomendado usar a abordagem tradicional.

2. Inferência de tipo local

Java 10 adicionou inferência de tipo para variáveis ​​locais. Antes disso, os desenvolvedores tinham que especificar os tipos duas vezes ao declarar e inicializar um objeto. Foi muito cansativo. Veja o exemplo a seguir:
Map> properties = new HashMap<>();
O tipo de informação de ambos os lados é indicado aqui. Se o definirmos em um só lugar, o leitor de código e o compilador Java entenderão facilmente que deve ser do tipo Map. A inferência de tipo local faz exatamente isso. Aqui está um exemplo:
var properties = new HashMap>();
Agora tudo é escrito apenas uma vez e o código não parece muito pior. E quando chamamos um método e armazenamos o resultado em uma variável, o código fica ainda mais curto. Exemplo:
var properties = getProperties();
E mais:
var countries = Set.of("Bangladesh", "Canada", "United States", "Tuvalu");
Embora a inferência de tipo local pareça um recurso conveniente, algumas pessoas a criticam. Alguns desenvolvedores argumentam que isso reduz a legibilidade. E isso é mais importante que a brevidade.

3. Expressões de switch avançadas

A instrução switch tradicional existe em Java desde o início e lembrava C e C++ naquela época. Tudo bem, mas conforme a linguagem evoluiu, esse operador não nos ofereceu nenhuma melhoria até o Java 14. É claro que ele tinha algumas desvantagens. O mais notório foi o fall -through: para resolver esse problema, os desenvolvedores usaram instruções break, que são em grande parte códigos clichê. No entanto, o Java 14 introduziu uma versão melhorada da instrução switch com uma lista muito maior de funções. Agora não precisamos mais adicionar instruções break e isso resolve o problema de falha. Além disso, uma instrução switch pode retornar um valor, o que significa que podemos usá-lo como uma expressão e atribuí-lo a uma variável.
int day = 5;
String result = switch (day) {
    case 1, 2, 3, 4, 5 -> "Weekday";
    case 6, 7 -> "Weekend";
    default -> "Unexpected value: " + day;
};

4. Registros

Embora Records seja um recurso relativamente novo introduzido no Java 16, muitos desenvolvedores o consideram muito útil, principalmente devido à criação de objetos imutáveis. Muitas vezes precisamos de objetos de dados em nosso programa para armazenar ou passar valores de um método para outro. Por exemplo, uma classe para transferência de coordenadas x, y e z, que escreveremos da seguinte forma:
package ca.bazlur.playground;

import java.util.Objects;

public final class Point {
    private final int x;
    private final int y;
    private final int z;

    public Point(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    public int x() {
        return x;
    }

    public int y() {
        return y;
    }

    public int z() {
        return z;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this) return true;
        if (obj == null || obj.getClass() != this.getClass()) return false;
        var that = (Point) obj;
        return this.x == that.x &&
                this.y == that.y &&
                this.z == that.z;
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y, z);
    }

    @Override
    public String toString() {
        return "Point[" +
                "x=" + x + ", " +
                "y=" + y + ", " +
                "z=" + z + ']';
    }

}
A classe parece muito detalhada. Com a ajuda de entradas, todo esse código pode ser substituído por uma versão mais concisa:
package ca.bazlur.playground;

public record Point(int x, int y, int z) {
}

5.Opcional

Um método é um contrato no qual definimos condições. Especificamos os parâmetros com seu tipo, bem como o tipo de retorno. Esperamos então que quando o método for chamado, ele se comporte de acordo com o contrato. No entanto, muitas vezes acabamos com null de um método em vez de um valor do tipo especificado. Isso é um erro. Para resolver isso, o iniciador normalmente testa o valor com uma condição if, independentemente de o valor ser nulo ou não. Exemplo:
public class Playground {

    public static void main(String[] args) {
        String name = findName();
        if (name != null) {
            System.out.println("Length of the name : " + name.length());
        }
    }

    public static String findName() {
        return null;
    }
}
Veja o código acima. O método findName deve retornar um String , mas retorna nulo. O iniciador agora deve primeiro verificar se há nulos para lidar com o problema. Se o iniciador esquecer de fazer isso, acabaremos recebendo um NullPointerException . Por outro lado, se a assinatura do método indicasse a possibilidade de não retorno, isso resolveria toda a confusão. E é aqui que a Opcional pode nos ajudar .
import java.util.Optional;

public class Playground {

    public static void main(String[] args) {
        Optional optionalName = findName();
        optionalName.ifPresent(name -> {
            System.out.println("Length of the name : " + name.length());
        });
    }

    public static Optional findName() {
        return Optional.empty();
    }
}
Aqui reescrevemos o método findName com uma opção Opcional para não retornar nenhum valor. Isso alerta os programadores com antecedência e corrige o problema.

6. API Java Data e Hora

Todo desenvolvedor fica confuso, de uma forma ou de outra, com o cálculo de data e hora. Isto não é um exagero. Isto ocorreu principalmente devido à falta de uma boa API Java para trabalhar com datas e horas. Agora esse problema não é mais relevante, pois o Java 8 introduziu um excelente conjunto de APIs no pacote java.time, que resolve todos os problemas relacionados a data e hora. O pacote java.time possui muitas interfaces e classes que eliminam a maioria dos problemas, incluindo fusos horários. As classes mais comumente usadas neste pacote são:
  • DataLocal
  • Horário local
  • DataLocalHora
  • Duração
  • Período
  • ZonedDateTime
Um exemplo de uso de classes do pacote java.time:
import java.time.LocalDate;
import java.time.Month;

public class Playground3 {
    public static void main(String[] args) {
        LocalDate date = LocalDate.of(2022, Month.APRIL, 4);
        System.out.println("year = " + date.getYear());
        System.out.println("month = " + date.getMonth());
        System.out.println("DayOfMonth = " + date.getDayOfMonth());
        System.out.println("DayOfWeek = " + date.getDayOfWeek());
        System.out.println("isLeapYear = " + date.isLeapYear());
    }
}
Um exemplo de uso da classe LocalTime para calcular o tempo:
LocalTime time = LocalTime.of(20, 30);
int hour = time.getHour();
int minute = time.getMinute();
time = time.withSecond(6);
time = time.plusMinutes(3);
Adicionando um fuso horário:
ZoneId zone = ZoneId.of("Canada/Eastern");
LocalDate localDate = LocalDate.of(2022, Month.APRIL, 4);
ZonedDateTime zonedDateTime = date.atStartOfDay(zone);

7.NullPointerException

Todo desenvolvedor odeia NullPointerException. Pode ser especialmente difícil quando o StackTrace não fornece informações úteis sobre qual é exatamente o problema. Para demonstrar isso, vamos dar uma olhada no código de exemplo:
package com.bazlur;

public class Main {

    public static void main(String[] args) {
        User user = null;
        getLengthOfUsersName(user);
    }

    public static void getLengthOfUsersName(User user) {
        System.out.println("Length of first name: " + user.getName().getFirstName());
    }
}

class User {
    private Name name;
    private String email;

    public User(Name name, String email) {
        this.name = name;
        this.email = email;
    }

   //getter
   //setter
}

class Name {
    private String firstName;
    private String lastName;

    public Name(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

   //getter
   //setter
}
Veja o método básico nesta passagem. Vemos que uma NullPointerException será lançada em seguida . Se executarmos e compilarmos o código em uma versão anterior ao Java 14, obteremos o seguinte StackTrace:
Exception in thread "main" java.lang.NullPointerException
at com.bazlur.Main.getLengthOfUsersName(Main.java:11)
at com.bazlur.Main.main(Main.java:7)
Há muito pouca informação aqui sobre onde e por que ocorreu NullPointerException . Mas no Java 14 e versões posteriores, obtemos muito mais informações no StackTrace, o que é muito conveniente. Em Java 14 veremos:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "ca.bazlur.playground.User.getName()" because "user" is null
at ca.bazlur.playground.Main.getLengthOfUsersName(Main.java:12)
at ca.bazlur.playground.Main.main(Main.java:8)

8. Futuro completável

Escrevemos programas linha por linha e eles geralmente são executados linha por linha. Mas há momentos em que precisamos de execução paralela para tornar o programa mais rápido. Para isso costumamos usar Java Thread. A programação de threads Java nem sempre envolve programação paralela. Em vez disso, nos dá a capacidade de compor vários módulos de programa independentes que serão executados de forma independente e muitas vezes até de forma assíncrona. No entanto, a programação de threads é bastante difícil, especialmente para iniciantes. É por isso que o Java 8 oferece uma API mais simples que permite executar parte de um programa de forma assíncrona. Vejamos um exemplo. Digamos que precisamos chamar três APIs REST e depois combinar os resultados. Podemos chamá-los um por um. Se cada um deles levar cerca de 200 milissegundos, o tempo total para recebê-los levará 600 milissegundos. E se pudéssemos executá-los em paralelo? Como os processadores modernos são tipicamente multi-core, eles podem lidar facilmente com três chamadas restantes em três processadores diferentes. Usando CompletableFuture podemos fazer isso facilmente.
package ca.bazlur.playground;

import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class SocialMediaService {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        var service = new SocialMediaService();

        var start = Instant.now();
        var posts = service.fetchAllPost().get();
        var duration = Duration.between(start, Instant.now());

        System.out.println("Total time taken: " + duration.toMillis());
    }

    public CompletableFuture> fetchAllPost() {
        var facebook = CompletableFuture.supplyAsync(this::fetchPostFromFacebook);
        var linkedIn = CompletableFuture.supplyAsync(this::fetchPostFromLinkedIn);
        var twitter = CompletableFuture.supplyAsync(this::fetchPostFromTwitter);

        var futures = List.of(facebook, linkedIn, twitter);

        return CompletableFuture.allOf(futures.toArray(futures.toArray(new CompletableFuture[0])))
                .thenApply(future -> futures.stream()
                        .map(CompletableFuture::join)
                        .toList());
    }
    private String fetchPostFromTwitter() {
        sleep(200);
        return "Twitter";
    }

    private String fetchPostFromLinkedIn() {
        sleep(200);
        return "LinkedIn";
    }

    private String fetchPostFromFacebook() {
        sleep(200);
        return "Facebook";
    }

    private void sleep(int millis) {
        try {
            TimeUnit.MILLISECONDS.sleep(millis);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

9. Expressões Lambda

As expressões lambda são talvez o recurso mais poderoso da linguagem Java. Eles mudaram a maneira como escrevemos código. Uma expressão lambda é como uma função anônima que pode receber argumentos e retornar um valor. Podemos atribuir uma função a uma variável e passá-la como argumento para um método, e o método pode retorná-la. Ele tem um corpo. A única diferença do método é que não há nome. As expressões são curtas e concisas. Eles geralmente não contêm muitos códigos clichê. Vejamos um exemplo onde precisamos listar todos os arquivos de um diretório com extensão .java.
var directory = new File("./src/main/java/ca/bazlur/playground");
String[] list = directory.list(new FilenameFilter() {
    @Override
    public boolean accept(File dir, String name) {
        return name.endsWith(".java");
    }
});
Se você observar atentamente este trecho de código, passamos a classe interna anônima list() para o método . E na classe interna colocamos a lógica de filtragem de arquivos. Essencialmente, estamos interessados ​​nesta parte da lógica, não no padrão em torno da lógica. A expressão lambda nos permite remover todo o template e podemos escrever o código que nos interessa. Aqui está um exemplo:
var directory = new File("./src/main/java/ca/bazlur/playground");
String[] list = directory.list((dir, name) -> name.endsWith(".java"));
Claro, este é apenas um exemplo; as expressões lambda têm muitos outros benefícios.

10. API de fluxo

No nosso trabalho diário, uma das tarefas comuns é processar um conjunto de dados. Possui várias operações comuns, como filtragem, transformação e coleta de resultados. Antes do Java 8, tais operações eram de natureza imperativa. Tivemos que escrever código de acordo com nossa intenção (ou seja, o que queríamos alcançar) e como gostaríamos de fazê-lo. Com a invenção da expressão lambda e da API Stream, agora podemos escrever funções de processamento de dados de forma declarativa. Apenas indicamos a nossa intenção e não precisamos anotar como obtemos o resultado. Aqui está um exemplo: Temos uma lista de livros e queremos encontrar todos os nomes dos livros Java, separados por vírgulas e ordenados.
public static String getJavaBooks(List books) {
    return books.stream()
            .filter(book -> Objects.equals(book.language(), "Java"))
            .sorted(Comparator.comparing(Book::price))
            .map(Book::name)
            .collect(Collectors.joining(", "));
}
O código acima é simples, legível e conciso. Mas abaixo você pode ver um código imperativo alternativo:
public static String getJavaBooksImperatively(List books) {
    var filteredBook = new ArrayList();
    for (Book book : books) {
        if (Objects.equals(book.language(), "Java")){
            filteredBook.add(book);
        }
    }
    filteredBook.sort(new Comparator() {
        @Override
        public int compare(Book o1, Book o2) {
            return Integer.compare(o1.price(), o2.price());
        }
    });

    var joiner = new StringJoiner(",");
    for (Book book : filteredBook) {
        joiner.add(book.name());
    }

    return joiner.toString();
}
Embora ambos os métodos retornem o mesmo valor, podemos ver claramente a diferença.
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION