JavaRush /Blog Java /Random-FR /Pause café #155. Top 10 des fonctions en Java

Pause café #155. Top 10 des fonctions en Java

Publié dans le groupe Random-FR

Top 10 des fonctions en Java

Source : DZone Cet article répertorie dix fonctionnalités de programmation Java souvent utilisées par les développeurs dans leur travail quotidien. Pause café #155.  Top 10 des fonctions en Java - 1

1. Méthode d'usine de collecte

Les collections sont l’une des fonctionnalités les plus couramment utilisées en programmation. Ils servent de conteneur dans lequel nous stockons des objets et les transmettons. Les collections sont également utilisées pour trier, rechercher et répéter des objets, ce qui facilite grandement la vie d'un programmeur. Ils disposent de plusieurs interfaces de base telles que List, Set, Map, ainsi que plusieurs implémentations. La manière traditionnelle de créer des collections et des cartes peut sembler verbeuse à de nombreux développeurs. C'est pourquoi Java 9 a introduit plusieurs méthodes de fabrique concises. Liste :

List countries = List.of("Bangladesh", "Canada", "United States", "Tuvalu"); 
Ensemble :

Set countries = Set.of("Bangladesh", "Canada", "United States", "Tuvalu");
Carte :
Map countriesByPopulation = Map.of("Bangladesh", 164_689_383,
                                                            "Canada", 37_742_154,
                                                            "United States", 331_002_651,
                                                            "Tuvalu", 11_792);
La méthode factory est très utile lorsque l’on souhaite créer des conteneurs immuables. Mais si vous envisagez de créer des collections mutables, il est recommandé d'utiliser l'approche traditionnelle.

2. Inférence de type local

Java 10 a ajouté l'inférence de type pour les variables locales. Avant cela, les développeurs devaient spécifier les types deux fois lors de la déclaration et de l'initialisation d'un objet. C'était très fatigant. Regardez l'exemple suivant :
Map> properties = new HashMap<>();
Le type d'informations des deux côtés est indiqué ici. Si nous le définissons en un seul endroit, alors le lecteur de code et le compilateur Java comprendront facilement qu'il doit s'agir d'un type Map. C’est exactement ce que fait l’inférence de type local. Voici un exemple :
var properties = new HashMap>();
Désormais, tout n’est écrit qu’une seule fois et le code n’a pas l’air bien pire. Et lorsque nous appelons une méthode et stockons le résultat dans une variable, le code devient encore plus court. Exemple:

var properties = getProperties();
Et plus loin:

var countries = Set.of("Bangladesh", "Canada", "United States", "Tuvalu");
Bien que l’inférence de type local semble être une fonctionnalité pratique, certaines personnes la critiquent. Certains développeurs affirment que cela réduit la lisibilité. Et c’est plus important que la brièveté.

3. Expressions de commutateur avancées

L'instruction switch traditionnelle existe en Java depuis le début et rappelait le C et le C++ à l'époque. C'était bien, mais au fur et à mesure de l'évolution du langage, cet opérateur ne nous a proposé aucune amélioration jusqu'à Java 14. Bien sûr, il présentait quelques inconvénients. Le plus connu était le fall -through : pour résoudre ce problème, les développeurs ont utilisé des instructions break, qui sont en grande partie du code passe-partout. Cependant, Java 14 a introduit une version améliorée de l'instruction switch avec une liste de fonctions beaucoup plus longue. Désormais, nous n'avons plus besoin d'ajouter des instructions break, ce qui résout le problème d'échec. De plus, une instruction switch peut renvoyer une valeur, ce qui signifie que nous pouvons l'utiliser comme expression et l'attribuer à une variable.

int day = 5;
String result = switch (day) {
    case 1, 2, 3, 4, 5 -> "Weekday";
    case 6, 7 -> "Weekend";
    default -> "Unexpected value: " + day;
};

4. Dossiers

Bien que Records soit une fonctionnalité relativement nouvelle introduite dans Java 16, de nombreux développeurs la trouvent très utile, principalement en raison de la création d'objets immuables. Nous avons souvent besoin d'objets de données dans notre programme pour stocker ou transmettre des valeurs d'une méthode à une autre. Par exemple, une classe de transfert de coordonnées x, y et z, que nous écrirons ainsi :

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 + ']';
    }

}
Le cours semble trop verbeux. A l'aide d'entrées, tout ce code peut être remplacé par une version plus concise :

package ca.bazlur.playground;

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

5.Facultatif

Une méthode est un contrat dans lequel on définit des conditions. Nous précisons les paramètres avec leur type, ainsi que le type de retour. Nous nous attendons alors à ce que lorsque la méthode est appelée, elle se comporte conformément au contrat. Cependant, nous nous retrouvons souvent avec null provenant d'une méthode au lieu d'une valeur du type spécifié. C'est une erreur. Pour résoudre ce problème, l'initiateur teste généralement la valeur avec une condition if, que la valeur soit nulle ou non. Exemple:

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;
    }
}
Regardez le code ci-dessus. La méthode findName est censée renvoyer un String , mais elle renvoie null. L'initiateur doit maintenant d'abord vérifier les valeurs nulles pour résoudre le problème. Si l'initiateur oublie de le faire, nous finirons par obtenir une NullPointerException . D'un autre côté, si la signature de la méthode indiquait la possibilité d'un non-retour, alors cela résoudrait toute la confusion. Et c'est là que Facultatif peut nous aider .

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();
    }
}
Ici, nous avons réécrit la méthode findName avec une option facultative pour ne renvoyer aucune valeur. Cela alerte les programmeurs à l'avance et résout le problème.

6. API Java Date Heure

Chaque développeur est confus à un degré ou à un autre avec le calcul de la date et de l'heure. Ce n’est pas une exagération. Cela était principalement dû au manque d’une bonne API Java pour travailler avec les dates et les heures. Désormais, ce problème n'est plus d'actualité, car Java 8 a introduit un excellent ensemble d'API dans le package java.time, qui résout tous les problèmes liés à la date et à l'heure. Le package java.time possède de nombreuses interfaces et classes qui éliminent la plupart des problèmes, y compris les fuseaux horaires. Les classes les plus couramment utilisées dans ce package sont :
  • Date Locale
  • Heure locale
  • DateHeure Locale
  • Durée
  • Période
  • ZonedDateTime
Un exemple d'utilisation des classes du package 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 exemple d'utilisation de la classe LocalTime pour calculer l'heure :

LocalTime time = LocalTime.of(20, 30);
int hour = time.getHour(); 
int minute = time.getMinute(); 
time = time.withSecond(6); 
time = time.plusMinutes(3);
Ajout d'un fuseau horaire :

ZoneId zone = ZoneId.of("Canada/Eastern");
LocalDate localDate = LocalDate.of(2022, Month.APRIL, 4);
ZonedDateTime zonedDateTime = date.atStartOfDay(zone);

7. NullPointerException

Chaque développeur déteste NullPointerException. Cela peut être particulièrement difficile lorsque StackTrace ne fournit pas d'informations utiles sur la nature exacte du problème. Pour illustrer cela, examinons l'exemple de code :

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
}
Regardez la méthode de base dans ce passage. Nous voyons qu'une NullPointerException sera lancée ensuite . Si nous exécutons et compilons le code dans une version antérieure à Java 14, nous obtiendrons le StackTrace suivant :

Exception in thread "main" java.lang.NullPointerException
at com.bazlur.Main.getLengthOfUsersName(Main.java:11)
at com.bazlur.Main.main(Main.java:7)
Il y a très peu d'informations ici sur où et pourquoi l' exception NullPointerException s'est produite . Mais dans Java 14 et les versions ultérieures, nous obtenons beaucoup plus d'informations dans StackTrace, ce qui est très pratique. En Java 14 nous verrons :

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. ComplétableFutur

Nous écrivons des programmes ligne par ligne et ils sont généralement exécutés ligne par ligne. Mais il y a des moments où nous avons besoin d’une exécution parallèle pour rendre le programme plus rapide. Pour cela, nous utilisons généralement Java Thread. La programmation de threads Java ne concerne pas toujours la programmation parallèle. Au lieu de cela, cela nous donne la possibilité de composer plusieurs modules de programme indépendants qui s'exécuteront de manière indépendante et souvent même de manière asynchrone. Cependant, la programmation de threads est assez difficile, surtout pour les débutants. C'est pourquoi Java 8 propose une API plus simple qui permet d'exécuter une partie d'un programme de manière asynchrone. Voyons un exemple. Disons que nous devons appeler trois API REST, puis combiner les résultats. Nous pouvons les appeler un par un. Si chacun d’eux prend environ 200 millisecondes, alors le temps total pour les recevoir prendra 600 millisecondes. Et si nous pouvions les faire fonctionner en parallèle ? Étant donné que les processeurs modernes sont généralement multicœurs, ils peuvent facilement gérer trois appels de repos sur trois processeurs différents. En utilisant CompletableFuture, nous pouvons le faire facilement.

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. Expressions lambda

Les expressions Lambda sont peut-être la fonctionnalité la plus puissante du langage Java. Ils ont changé la façon dont nous écrivons le code. Une expression lambda est comme une fonction anonyme qui peut prendre des arguments et renvoyer une valeur. Nous pouvons attribuer une fonction à une variable et la transmettre comme arguments à une méthode, et la méthode peut la renvoyer. Il a un corps. La seule différence avec la méthode est qu’il n’y a pas de nom. Les expressions sont courtes et concises. Ils ne contiennent généralement pas beaucoup de code passe-partout. Voyons un exemple où nous devons lister tous les fichiers d'un répertoire avec l'extension .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 vous regardez attentivement ce morceau de code, nous avons transmis la classe interne anonyme list() à la méthode . Et dans la classe interne, nous avons placé la logique de filtrage des fichiers. Essentiellement, nous nous intéressons à cette partie de la logique, et non au modèle qui entoure la logique. L'expression lambda nous permet de supprimer l'intégralité du modèle et nous pouvons écrire le code qui nous intéresse. Voici un exemple :

var directory = new File("./src/main/java/ca/bazlur/playground");
String[] list = directory.list((dir, name) -> name.endsWith(".java"));
Bien sûr, ce n’est qu’un exemple ; les expressions lambda présentent de nombreux autres avantages.

10. API de flux

Dans notre travail quotidien, l’une des tâches courantes consiste à traiter un ensemble de données. Il comporte plusieurs opérations courantes telles que le filtrage, la transformation et la collecte des résultats. Avant Java 8, de telles opérations étaient de nature impérative. Nous avons dû écrire du code pour notre intention (c'est-à-dire ce que nous voulions réaliser) et comment nous aimerions le faire. Avec l'invention de l'expression lambda et de l'API Stream, nous pouvons désormais écrire des fonctions de traitement de données de manière déclarative. Nous indiquons seulement notre intention et nous n'avons pas besoin d'écrire comment nous obtenons le résultat. Voici un exemple : Nous avons une liste de livres et nous souhaitons retrouver tous les noms des livres Java, séparés par des virgules et triés.
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(", "));
}
Le code ci-dessus est simple, lisible et concis. Mais ci-dessous, vous pouvez voir un code impératif alternatif :
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();
}
Bien que les deux méthodes renvoient la même valeur, nous pouvons clairement voir la différence.
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION