JavaRush /Blog Java /Random-FR /Comparateur en Java
Viacheslav
Niveau 3

Comparateur en Java

Publié dans le groupe Random-FR
Seuls les paresseux n’ont pas écrit sur Comparator et la comparaison en Java. Je ne suis pas paresseux, alors je vous demande d'aimer et de privilégier une variante supplémentaire. 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 qu'après avoir lu cet article, tout le monde pourra écrire un comparateur de mémoire.
Comparateur en Java - 1
Introduction Java est connu pour être un langage orienté objet. En conséquence, en Java, il est courant d’opérer avec des objets. Mais tôt ou tard, la tâche de comparer des objets selon un principe se pose. Donc, étant donné : nous avons un message, qui est décrit par la classe Message :
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;
    }
}
Ajoutons cette classe au compilateur Java Tutorialspoint . N'oublions pas également d'ajouter des importations :
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
Dans la méthode principale, nous allons créer 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 devrions faire si nous voulons les comparer ? Par exemple, nous voulons trier par identifiant. Et pour créer de l'ordre, vous devez d'une manière ou d'une autre comparer les objets afin de comprendre quel objet est le précédent (c'est-à-dire le plus petit) et lequel est le suivant (c'est-à-dire le plus grand). Commençons par une classe comme java.lang.Object . Comme nous le savons, toutes les classes héritent implicitement de cette classe Object. Et c'est logique, car Cela exprime essentiellement le concept : « Tout est un objet » et fournit un comportement commun à toutes les classes. Et cette classe définit que chaque classe a deux méthodes : → hashCode La méthode hashCode renvoie une représentation numérique (int) de l'objet en tant qu'instance de la classe. Qu'est-ce que ça veut dire? Cela signifie que si vous avez créé deux instances différentes d'une classe, puisque les instances sont différentes, leur hashCode doit être différent. Voici ce qu'il est dit dans la description de la méthode : "Autant que cela soit raisonnablement pratique, la méthode hashCode définie par la classe Object renvoie des entiers distincts pour des objets distincts". Autrement dit, s'il s'agit de deux instances différentes, alors elles devraient avoir des valeurs différentes. Codes de hachage. Autrement dit, cette méthode ne convient pas à notre comparaison. → égal à La méthode égal répond à la question « les objets sont-ils égaux » et renvoie un booléen. Cette méthode a le code par défaut :
public boolean equals(Object obj) {
    return (this == obj);
}
Autrement dit, sans écraser cette méthode sur un objet, cette méthode indique essentiellement si les références à l'objet correspondent ou non. Cela ne convient pas à nos messages, car nous ne sommes pas intéressés par les liens vers l'objet, nous nous intéressons à l'identifiant du message. Et même si nous remplaçons la méthode égale, le maximum que nous obtiendrons est : « Ils sont égaux » ou « Ils ne sont pas égaux ». Mais cela ne nous suffit pas pour déterminer l’ordre.

Comparateur et comparable en Java

Qu'est-ce qui nous convient ? Si nous traduisons le mot « comparer » en anglais dans le traducteur, nous obtiendrons la traduction « comparer ». Super, alors nous avons besoin de quelqu'un qui comparera. Si vous comparez cette comparaison, alors celui qui compare est le Comparateur. Ouvrons Java Api et trouvons Comparator là-bas . Et en effet, il existe une telle interface - java.util.Comparator java.util.Comparator et java.lang.Comparable Comme vous pouvez le voir, il existe une telle interface. La classe qui l'implémente dit : « J'implémente une fonction pour comparer des objets ». La seule chose à vraiment retenir est le contrat comparateur, qui s’exprime ainsi :

Comparator возвращает int по следующей схеме: 
  • отрицательный int (первый an object отрицательный, то есть меньше)
  • положительный int (первый an object положительный, хороший, то есть больший)
  • ноль = an objectы равны
Écrivons maintenant un comparateur. Nous devrons importer java.util.Comparator . Après l'importation, ajoutez une méthode à main : Comparator<Message> comparator = new Comparator<Message>(); Naturellement, cela ne fonctionnera pas, car Le comparateur est une interface. Par conséquent, après les parenthèses, nous ajouterons des bouclées { }. Entre ces parenthèses nous écrirons la méthode :
public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
Vous n'avez même pas besoin de vous rappeler d'écrire ceci. Un comparateur est celui qui effectue une comparaison, c'est-à-dire qui fait une comparaison. Pour répondre à la question de savoir dans quel ordre se trouvent les objets comparés, nous revenons à int. C'est tout, en fait. Simplement et facilement. Comme nous pouvons le voir sur l'exemple, en plus de Comparator, il existe une autre interface - java.lang.Comparable , implémentant laquelle nous devons définir la méthode compareTo . Cette interface indique qu'"Une classe qui implémente une interface permet de comparer les instances de la classe". Par exemple, l'implémentation de compareTo par Integer ressemble à ceci :
(x < y) ? -1 : ((x == y) ? 0 : 1)
Comment mémoriser toutes ces interfaces ? Pourquoi? Tout vient de l'anglais. Comparer - pour comparer, celui qui compare est Comparateur (en tant que registraire, par exemple. C'est-à-dire celui qui enregistre), et l'adjectif « comparé » est Comparable. Eh bien, « Comparer avec » se traduit non seulement par comparer avec, mais aussi par comparer à. C'est simple. Le langage Java a été écrit par des anglophones, et en nommant tout en Java, ils étaient simplement guidés par l'anglais et il y avait une sorte de logique dans la dénomination. Et la méthode compareTo décrit comment une instance d'une classe doit être comparée à d'autres instances. Par exemple, les chaînes sont comparées lexigraphiquement et les nombres sont comparés par valeur.
Comparateur en Java - 2
Java 8 a apporté quelques changements intéressants. Si nous regardons attentivement l’interface du Comparator, nous verrons qu’il y a une annotation au-dessus @FunctionalInterface. En fait, cette annotation est à titre informatif et signifie que cette interface est fonctionnelle. Cela signifie que cette interface n'a qu'une seule méthode abstraite 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());
Entre parenthèses est la façon dont nous nommons les variables. Java lui-même le verra parce que... S'il n'existe qu'une seule méthode, il est alors clair quels paramètres d'entrée sont nécessaires, combien et quels types. Ensuite, nous disons avec une flèche que nous voulons les transférer vers cette section du code. De plus, grâce à Java 8, des méthodes par défaut sont apparues dans les interfaces - ce sont des méthodes qui apparaissent par défaut (par défaut) lorsque l'on implémente une interface. Il en existe plusieurs dans l'interface du Comparateur. Par exemple :
Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
Il existe une autre méthode qui rendra votre code plus propre. Regardons l'exemple ci-dessus, où nous avons décrit notre comparateur. Que fait-il? C'est assez primitif. Il prend simplement un objet et en extrait une valeur comparable. Par exemple, Integer implémente comparable, nous avons donc pu effectuer compareTo sur les valeurs d'ID de message. Cette simple fonction de comparaison peut également s’écrire comme ceci :
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Autrement dit, « Nous avons un comparateur qui compare comme ceci : il prend des objets, en obtient Comparable à l'aide de la méthode getId(), compare à l'aide de compareTo. » Et fini les designs terribles. Et enfin, je voudrais souligner une autre fonctionnalité. Les comparateurs peuvent être enchaînés. Par exemple:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

Application

La déclaration du comparateur s’est avérée assez logique, n’est-ce pas ? Reste maintenant à voir comment l’utiliser et à quels endroits. → Collections.sort (java.util.Collections) Bien sûr, nous pouvons trier les collections de cette façon. Mais pas tout, juste des listes. Et il n'y a rien d'inhabituel ici, parce que... C'est la liste qui nécessite l'accès à un élément par index. Et cela permet d'échanger l'élément numéro deux avec l'élément numéro trois. Par conséquent, ce tri n’est possible que pour les listes :
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort (java.util.Arrays) Les tableaux sont également pratiques à trier. Encore une fois, pour la même raison d'accéder aux éléments par index. → Descendants de java.util.SortedSet et java.util.SortedMap On s'en souvient, Set et Map ne garantissent pas l'ordre de stockage des enregistrements. MAIS nous avons des implémentations spéciales qui garantissent l'ordre. Et si les éléments de la collection n'implémentent pas java.lang.Comparable, alors nous pouvons transmettre Comparator au constructeur de ces collections :
Set<Message> msgSet = new TreeSet(comparator);
API Stream Dans l'API Stream, apparue dans Java 8, un comparateur permet de simplifier le travail sur les éléments de flux. Par exemple, nous avons 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, mais il y a des problèmes plus intéressants. Par exemple, vous devez préparer une carte, où la clé est l'identifiant du message. En même temps, nous souhaitons trier ces clés afin qu'elles soient dans l'ordre, de la plus petite à la plus grande. Commençons par ce code :
Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
Ce que nous reviendrons ici est en fait une HashMap. Et comme nous le savons, cela ne garantit aucune commande. Par conséquent, nos dossiers triés par ID sont tout simplement devenus dans le désordre. 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 avait l'air un peu plus effrayant, mais le problème a maintenant été résolu correctement grâce à l'implémentation explicite de TreeMap. Vous pouvez en savoir plus sur les différents groupes ici : Vous pouvez créer le collecteur vous-même. Vous pouvez en savoir plus ici : "Création d'un collecteur personnalisé en Java 8" . Et il est utile de lire la discussion ici : "Liste Java 8 à mapper avec flux" .
Comparateur en Java - 3
Les râteaux comparatifs et comparables sont bons. Mais il y a une nuance qui leur est associée et qui mérite d'être rappelée. Lorsqu'une classe effectue un tri, elle calcule qu'elle peut convertir votre classe en Comparable. Si ce n'est pas le cas, vous recevrez une erreur au moment de l'exécution. Regardons un exemple :
SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
Il semble qu'il n'y ait rien de mal ici. Mais en fait, dans notre exemple, il plantera avec l'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). Et je ne pouvais pas. Vous devez vous en souvenir lorsque vous travaillez avec SortedMap et SortedSet. En outre recommandé pour la visualisation : Yuri Tkach : HashSet et TreeSet - Collections #1 - Java avancé
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION