CodeGym /Blog Java /Random-FR /L'interface de comparaison de Java
Auteur
Artem Divertitto
Senior Android Developer at United Tech

L'interface de comparaison de Java

Publié dans le groupe Random-FR
Les paresseux ne sont pas les seuls à écrire sur les comparateurs et les comparaisons en Java. Je ne suis pas paresseux, alors s'il vous plaît, aimez et râlez pour une autre explication. J'espère que ce ne sera pas superflu. Et oui, cet article est la réponse à la question : « Pouvez-vous écrire un comparateur de mémoire ? » J'espère que tout le monde pourra écrire un comparateur de mémoire après avoir lu cet article. Interface comparateur Javas - 1

Introduction

Comme vous le savez, Java est un langage orienté objet. Par conséquent, il est courant de manipuler des objets en Java. Mais tôt ou tard, vous êtes confronté à la tâche de comparer des objets en fonction de certaines caractéristiques. Par exemple : Supposons que nous ayons un message décrit par la Messageclasse :

public static class Message {
    private String message;
    private int id;
        
    public Message(String message) {
        this.message = message;
        this.id = new Random().nextInt(1000);
    }
    public String getMessage() {
        return message;
    }
    public Integer getId() {
        return id;
    }
    public String toString() {
        return "[" + id + "] " + message;
    }
}
Placez cette classe dans le compilateur Java Tutorialspoint . N'oubliez pas d'ajouter également les instructions d'importation :

import java.util.Random;
import java.util.ArrayList;
import java.util.List;
Dans la mainméthode, créez plusieurs messages :

public static void main(String[] args){
    List<Message> messages = new ArrayList();
    messages.add(new Message("Hello, World!"));
    messages.add(new Message("Hello, Sun!"));
    System.out.println(messages);
}
Réfléchissons à ce que nous ferions si nous voulions les comparer ? Par exemple, nous voulons trier par identifiant. Et pour créer un ordre, nous devons en quelque sorte comparer les objets afin de comprendre quel objet doit venir en premier (c'est-à-dire le plus petit) et lequel doit suivre (c'est-à-dire le plus grand). Commençons par une classe comme java.lang.Object . Nous savons que toutes les classes héritent implicitement de la Objectclasse. Et cela a du sens car cela reflète le concept selon lequel "tout est un objet" et fournit un comportement commun à toutes les classes. Cette classe dicte que chaque classe a deux méthodes : → hashCode La hashCodeméthode renvoie des valeurs numériques (int) représentation de l'objet. Qu'est-ce que cela signifie? Cela signifie que si vous créez deux instances différentes d'une classe, elles doivent avoir hashCodedes s différents. La description de la méthode en dit autant : "Dans la mesure du possible, la méthode hashCode définie par la classe Object renvoie des entiers distincts pour des objets distincts". En d'autres termes, pour deux instances différents, il devrait y avoir hashCodedes s différents. C'est-à-dire que cette méthode ne convient pas à notre comparaison. → equals. La equalsméthode répond à la question « ces objets sont-ils égaux ? et renvoie un boolean." Par défaut, cette méthode a le code suivant :

public boolean equals(Object obj) {
    return (this == obj);
}
Autrement dit, si cette méthode n'est pas remplacée, elle indique essentiellement si les références d'objet correspondent ou non. Ce n'est pas ce que nous voulons pour nos messages, car nous nous intéressons aux identifiants de message, pas aux références d'objet. Et même si nous outrepassons la equalsméthode, le mieux que nous puissions espérer est de savoir s'ils sont égaux. Et cela ne nous suffit pas pour déterminer l'ordre. Alors, de quoi avons-nous besoin ? Nous avons besoin de quelque chose qui se compare. Celui qui compare est un Comparator. Ouvrez l' API Java et recherchez Comparator . En effet, il existe une java.util.Comparatorinterface java.util.Comparator and java.util.Comparable Comme vous pouvez le voir, une telle interface existe. Une classe qui l'implémente dit : "J'implémente une méthode qui compare des objets". La seule chose dont vous devez vraiment vous souvenir est le contrat de comparaison, qui s'exprime comme suit :

Comparator returns an int according to the following rules: 
  • It returns a negative int if the first object is smaller
  • It returns a positive int if the first object is larger
  • It returns zero if the objects are equal
Écrivons maintenant un comparateur. Nous devrons importer java.util.Comparator. Après l'instruction d'importation, ajoutez ce qui suit à la mainméthode : Comparator<Message> comparator = new Comparator<Message>(); bien sûr, cela ne fonctionnera pas, car Comparatoril s'agit d'une interface. Nous ajoutons donc des accolades {}après les parenthèses. Écrivez la méthode suivante entre les accolades :

public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
Vous n'avez même pas besoin de vous souvenir de l'orthographe. Un comparateur est celui qui effectue une comparaison, c'est-à-dire qu'il compare. Pour indiquer l'ordre relatif des objets, nous renvoyons un int. C'est fondamentalement ça. Agréable et facile. Comme vous pouvez le voir dans l'exemple, en plus de Comparator, il existe une autre interface — java.lang.Comparable, qui nous oblige à implémenter la compareTométhode. Cette interface dit, "une classe qui m'implémente permet de comparer des instances de la classe". Par exemple, Integerl'implémentation de compareTo est la suivante :

(x < y) ? -1 : ((x == y) ? 0 : 1)
Java 8 a introduit quelques modifications intéressantes. Si vous regardez de plus près l' Comparatorinterface, vous verrez l' @FunctionalInterfaceannotation au-dessus. Cette annotation est à titre indicatif et nous indique que cette interface est fonctionnelle. Cela signifie que cette interface n'a qu'une seule méthode abstraite, qui est une méthode sans implémentation. Qu'est-ce que cela nous donne ? Nous pouvons maintenant écrire le code du comparateur comme ceci :

Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
Nous nommons les variables entre parenthèses. Java verra que, comme il n'y a qu'une seule méthode, le nombre requis et les types de paramètres d'entrée sont clairs. Ensuite, nous utilisons l'opérateur flèche pour les transmettre à cette partie du code. De plus, grâce à Java 8, nous avons maintenant des méthodes par défaut dans les interfaces. Ces méthodes apparaissent par défaut lorsque nous implémentons une interface. L' Comparatorinterface en a plusieurs. Par exemple:

Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
Il existe une autre méthode qui rendra votre code plus propre. Jetez un œil à l'exemple ci-dessus, où nous avons défini notre comparateur. Qu'est ce que ça fait? C'est assez primitif. Il prend simplement un objet et en extrait une valeur "comparable". Par exemple, Integerimplements comparable, nous pouvons donc effectuer une opération compareTo sur les valeurs des champs d'ID de message. Cette fonction de comparaison simple peut être écrite comme ceci :

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
En d'autres termes, nous avons a Comparatorqui compare comme ceci : il prend des objets, utilise la getId()méthode pour en obtenir a Comparable, puis utilise compareTopour comparer. Et il n'y a plus de constructions horribles. Et enfin, je veux noter une autre caractéristique. Les comparateurs peuvent être chaînés. Par exemple:

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

Application

Déclarer un comparateur s'avère assez logique, vous ne trouvez pas ? Maintenant, nous devons voir comment et où l'utiliser. → Collections.sort(java.util.Collections) Nous pouvons, bien sûr, trier les collections de cette façon. Mais pas toutes les collections, seulement des listes. Il n'y a rien d'inhabituel ici, car les listes sont le genre de collections où vous accédez aux éléments par leur index. Cela permet d'échanger le deuxième élément avec le troisième élément. C'est pourquoi la méthode de tri suivante ne s'applique qu'aux listes :

Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort(java.util.Arrays) Les tableaux sont également faciles à trier. Encore une fois, pour la même raison — leurs éléments sont accessibles par index. → Descendants of java.util.SortedSet and java.util.SortedMap Vous vous en souviendrez Setet Mapne garantissez pas l'ordre dans lequel les éléments sont stockés. MAIS, nous avons des implémentations spéciales qui garantissent la commande. Et si les éléments d'une collection n'implémentent pas java.util.Comparable, alors nous pouvons passer a Comparatorà son constructeur :

Set<Message> msgSet = new TreeSet(comparator);
Stream API Dans l'API Stream, apparue dans Java 8, les comparateurs permettent de simplifier le travail avec les éléments de flux. Par exemple, supposons que nous ayons besoin d'une séquence de nombres aléatoires de 0 à 999 inclus :

Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
    .limit(10)
    .sorted(Comparator.naturalOrder())
    .forEach(e -> System.out.println(e));
On pourrait s'arrêter là, mais il y a des problèmes encore plus intéressants. Par exemple, supposons que vous deviez préparer un Map, où la clé est un identifiant de message. De plus, nous voulons trier ces clés, nous allons donc commencer par le code suivant :

Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
Nous obtenons en fait un HashMapici. Et comme nous le savons, cela ne garantit aucune commande. En conséquence, nos éléments, qui étaient triés par identifiant, perdent tout simplement leur ordre. Pas bon. Il va falloir changer un peu notre collecteur :

Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg, (oldValue, newValue) -> oldValue, TreeMap::new));
Le code a commencé à paraître un peu plus effrayant, mais maintenant le problème est résolu correctement. En savoir plus sur les différents regroupements ici : Vous pouvez créer votre propre collecteur. En savoir plus ici : "Création d'un collecteur personnalisé dans Java 8" . Et vous bénéficierez de la lecture de la discussion ici : "Java 8 list to map with stream" .

Piège à chute

Comparatoret Comparablesont bons. Mais il y a une nuance dont vous devez vous souvenir. Lorsqu'une classe effectue un tri, elle s'attend à ce que votre classe puisse être convertie en un fichier Comparable. Si ce n'est pas le cas, vous recevrez une erreur lors de l'exécution. Regardons un exemple :

SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
Il semble que rien ne va pas ici. Mais en fait, dans notre exemple, il échouera avec une erreur : java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable Et tout cela parce qu'il a essayé de trier les éléments (c'est un SortedSet, après tout)... mais n'a pas pu. Ne l'oubliez pas lorsque vous travaillez avec SortedMapet SortedSet.

Plus de lecture :

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION