JavaRush /Blog Java /Random-ES /Pausa para el café #155. Las 10 funciones principales en ...

Pausa para el café #155. Las 10 funciones principales en Java

Publicado en el grupo Random-ES

Las 10 funciones principales en Java

Fuente: DZone Este artículo enumera diez funciones de programación Java que los desarrolladores suelen utilizar en su trabajo diario. Pausa para el café #155.  Las 10 funciones principales en Java - 1

1. Método de fábrica de recolección

Las colecciones son una de las funciones más utilizadas en programación. Se utilizan como contenedor en el que almacenamos objetos y los transmitimos. Las colecciones también se utilizan para ordenar, buscar y repetir objetos, lo que facilita mucho la vida del programador. Tienen varias interfaces básicas como List, Set, Map, así como varias implementaciones. La forma tradicional de crear colecciones y mapas puede parecer detallada para muchos desarrolladores. Es por eso que Java 9 introdujo varios métodos de fábrica concisos. Lista :
List countries = List.of("Bangladesh", "Canada", "United States", "Tuvalu");
Colocar :
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);
El método de fábrica es muy útil cuando queremos crear contenedores inmutables. Pero si va a crear colecciones mutables, se recomienda utilizar el enfoque tradicional.

2. Inferencia de tipos locales

Java 10 agregó inferencia de tipos para variables locales. Antes de esto, los desarrolladores tenían que especificar tipos dos veces al declarar e inicializar un objeto. Fue muy agotador. Mira el siguiente ejemplo:
Map> properties = new HashMap<>();
Aquí se indica el tipo de información de ambas caras. Si lo definimos en un lugar, entonces el lector de código y el compilador de Java entenderán fácilmente que debe ser un tipo de mapa. La inferencia de tipos locales hace precisamente eso. He aquí un ejemplo:
var properties = new HashMap>();
Ahora todo está escrito una sola vez y el código no se ve mucho peor. Y cuando llamamos a un método y almacenamos el resultado en una variable, el código se vuelve aún más corto. Ejemplo:
var properties = getProperties();
Y además:
var countries = Set.of("Bangladesh", "Canada", "United States", "Tuvalu");
Aunque la inferencia de tipos locales parece una característica conveniente, algunas personas la critican. Algunos desarrolladores argumentan que esto reduce la legibilidad. Y esto es más importante que la brevedad.

3. Expresiones de cambio avanzadas

La declaración de cambio tradicional ha existido en Java desde el principio y recordaba a C y C++ en aquel entonces. Esto estaba bien, pero a medida que el lenguaje evolucionó, este operador no nos ofreció ninguna mejora hasta Java 14. Eso sí, tenía algunas desventajas. El más notorio fue el fracaso : para resolver este problema, los desarrolladores utilizaron declaraciones de interrupción, que son en gran medida código repetitivo. Sin embargo, Java 14 introdujo una versión mejorada de la declaración de cambio con una lista mucho más grande de funciones. Ahora ya no necesitamos agregar declaraciones de interrupción y esto resuelve el problema de falla. Además, una declaración de cambio puede devolver un valor, lo que significa que podemos usarlo como expresión y asignarlo a una variable.
int day = 5;
String result = switch (day) {
    case 1, 2, 3, 4, 5 -> "Weekday";
    case 6, 7 -> "Weekend";
    default -> "Unexpected value: " + day;
};

4. Registros

Aunque Registros es una característica relativamente nueva introducida en Java 16, muchos desarrolladores la encuentran muy útil, principalmente debido a la creación de objetos inmutables. A menudo necesitamos objetos de datos en nuestro programa para almacenar o pasar valores de un método a otro. Por ejemplo, una clase para transferir coordenadas x, y, z, que escribiremos de la siguiente manera:
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 + ']';
    }

}
La clase parece demasiado detallada. Con la ayuda de entradas, todo este código se puede reemplazar con una versión más concisa:
package ca.bazlur.playground;

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

5.Opcional

Un método es un contrato en el que definimos condiciones. Especificamos los parámetros con su tipo, así como el tipo de retorno. Luego esperamos que cuando se llame al método, se comporte de acuerdo con el contrato. Sin embargo, a menudo terminamos con un valor nulo de un método en lugar de un valor del tipo especificado. Esto es un error. Para resolver esto, el iniciador normalmente prueba el valor con una condición if, independientemente de si el valor es nulo o no. Ejemplo:
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;
    }
}
Mira el código anterior. Se supone que el método findName devuelve un String , pero devuelve nulo. El iniciador ahora debe verificar primero si hay nulos para solucionar el problema. Si el iniciador se olvida de hacer esto, terminaremos obteniendo una NullPointerException . Por otro lado, si la firma del método indicara la posibilidad de no retorno, entonces esto resolvería toda la confusión. Y aquí es donde Opcional puede ayudarnos .
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();
    }
}
Aquí hemos reescrito el método findName con una opción opcional para no devolver ningún valor. Esto alerta a los programadores con anticipación y soluciona el problema.

6. API de fecha y hora de Java

Todo desarrollador se confunde en un grado u otro con el cálculo de la fecha y la hora. Esto no es una exageración. Esto se debió principalmente a la falta de una buena API de Java para trabajar con fechas y horas. Ahora este problema ya no es relevante, porque Java 8 introdujo un excelente conjunto de API en el paquete java.time, que resuelve todos los problemas relacionados con la fecha y la hora. El paquete java.time tiene muchas interfaces y clases que eliminan la mayoría de los problemas, incluidas las zonas horarias. Las clases más utilizadas en este paquete son:
  • Fecha local
  • Hora local
  • Fecha y hora local
  • Duración
  • Período
  • ZonaDateTime
Un ejemplo de uso de clases del paquete 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());
    }
}
Un ejemplo del uso de la clase LocalTime para calcular el tiempo:
LocalTime time = LocalTime.of(20, 30);
int hour = time.getHour();
int minute = time.getMinute();
time = time.withSecond(6);
time = time.plusMinutes(3);
Agregar una zona horaria:
ZoneId zone = ZoneId.of("Canada/Eastern");
LocalDate localDate = LocalDate.of(2022, Month.APRIL, 4);
ZonedDateTime zonedDateTime = date.atStartOfDay(zone);

7.Excepción de puntero nulo

Todo desarrollador odia NullPointerException. Puede resultar especialmente difícil cuando StackTrace no proporciona información útil sobre cuál es exactamente el problema. Para demostrar esto, echemos un vistazo al código de muestra:
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
}
Mire el método básico en este pasaje. Vemos que a continuación se lanzará una NullPointerException . Si ejecutamos y compilamos el código en una versión anterior a Java 14, obtendremos el siguiente StackTrace:
Exception in thread "main" java.lang.NullPointerException
at com.bazlur.Main.getLengthOfUsersName(Main.java:11)
at com.bazlur.Main.main(Main.java:7)
Aquí hay muy poca información sobre dónde y por qué ocurrió la excepción NullPointerException . Pero en Java 14 y versiones posteriores, obtenemos mucha más información en StackTrace, lo cual es muy conveniente. En 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 completable

Escribimos programas línea por línea y normalmente se ejecutan línea por línea. Pero hay ocasiones en las que necesitamos una ejecución paralela para que el programa sea más rápido. Para ello solemos utilizar Java Thread. La programación de subprocesos Java no siempre se trata de programación paralela. En cambio, nos brinda la capacidad de componer varios módulos de programa independientes que se ejecutarán de forma independiente y, a menudo, incluso de forma asincrónica. Sin embargo, la programación de subprocesos es bastante difícil, especialmente para los principiantes. Es por eso que Java 8 ofrece una API más simple que le permite ejecutar parte de un programa de forma asincrónica. Veamos un ejemplo. Digamos que necesitamos llamar a tres API REST y luego combinar los resultados. Podemos llamarlos uno por uno. Si cada uno de ellos tarda unos 200 milisegundos, entonces el tiempo total para recibirlos será de 600 milisegundos. ¿Y si pudiéramos ejecutarlos en paralelo? Dado que los procesadores modernos suelen ser multinúcleo, pueden manejar fácilmente tres llamadas de descanso en tres procesadores diferentes. Usando CompletableFuture podemos hacer esto fácilmente.
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. Expresiones Lambda

Las expresiones lambda son quizás la característica más poderosa del lenguaje Java. Cambiaron la forma en que escribimos código. Una expresión lambda es como una función anónima que puede tomar argumentos y devolver un valor. Podemos asignar una función a una variable y pasarla como argumentos a un método, y el método puede devolverla. Tiene un cuerpo. La única diferencia con el método es que no hay nombre. Las expresiones son breves y concisas. Por lo general, no contienen mucho código repetitivo. Veamos un ejemplo en el que necesitamos enumerar todos los archivos en un directorio con extensión .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");
    }
});
Si observa detenidamente este fragmento de código, hemos pasado la lista de clases interna anónima () al método . Y en la clase interna colocamos la lógica para filtrar archivos. Esencialmente, estamos interesados ​​en esta parte de la lógica, no en el patrón que rodea la lógica. La expresión lambda nos permite eliminar toda la plantilla y podemos escribir el código que nos interese. He aquí un ejemplo:
var directory = new File("./src/main/java/ca/bazlur/playground");
String[] list = directory.list((dir, name) -> name.endsWith(".java"));
Por supuesto, este es sólo un ejemplo; las expresiones lambda tienen muchos otros beneficios.

10. API de transmisión

En nuestro trabajo diario, una de las tareas habituales es procesar un conjunto de datos. Tiene varias operaciones comunes como filtrar, transformar y recopilar resultados. Antes de Java 8, este tipo de operaciones eran de naturaleza imprescindible. Tuvimos que escribir código para nuestra intención (es decir, lo que queríamos lograr) y cómo nos gustaría hacerlo. Con la invención de la expresión lambda y Stream API, ahora podemos escribir funciones de procesamiento de datos de forma declarativa. Solo indicamos nuestra intención y no necesitamos anotar cómo obtenemos el resultado. Aquí hay un ejemplo: tenemos una lista de libros y queremos encontrar todos los nombres de los libros de Java, separados por comas y 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(", "));
}
El código anterior es simple, legible y conciso. Pero a continuación puedes ver un 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();
}
Aunque ambos métodos devuelven el mismo valor, podemos ver claramente la diferencia.
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION