Pourquoi une classe Java devrait-elle implémenter comparable?

Réponses:

213

Voici un échantillon réel. Notez que Stringimplémente également Comparable.

class Author implements Comparable<Author>{
    String firstName;
    String lastName;

    @Override
    public int compareTo(Author other){
        // compareTo should return < 0 if this is supposed to be
        // less than other, > 0 if this is supposed to be greater than 
        // other and 0 if they are supposed to be equal
        int last = this.lastName.compareTo(other.lastName);
        return last == 0 ? this.firstName.compareTo(other.firstName) : last;
    }
}

plus tard..

/**
 * List the authors. Sort them by name so it will look good.
 */
public List<Author> listAuthors(){
    List<Author> authors = readAuthorsFromFileOrSomething();
    Collections.sort(authors);
    return authors;
}

/**
 * List unique authors. Sort them by name so it will look good.
 */
public SortedSet<Author> listUniqueAuthors(){
    List<Author> authors = readAuthorsFromFileOrSomething();
    return new TreeSet<Author>(authors);
}
Enno Shioji
la source
15
Je veux juste noter que vous voudrez souvent remplacer equals(et donc hashCode) être cohérent avec votre compareTométhode. Par exemple, cela est nécessaire si vous voulez que la classe joue bien avec un TreeSet.
pidge
Pourquoi ne pas simplement revenir last?
Anirban Nag 'tintinmj'
@ AnirbanNag'tintinmj 'pour trier automatiquement par prénom au cas où le nom de famille serait le même.
OddDev
Plus un pour la bonne explication de la raison pour laquelle compareTo renvoie un int et ce que cela signifie. Le plus utile.
james.garriss
1
@ user3932000: C'est vrai, c'est à peu près le but de toutes les interfaces. Mais notez que les "autres méthodes Java" incluent les méthodes écrites par l'utilisateur! En fait, je dirais que la majorité des interfaces sont consommées par le code de l'utilisateur. Dans les bases de code plus grandes, "vous-même" devient rapidement "les autres"
Enno Shioji
40

Comparable définit un ordre naturel. Cela signifie que vous le définissez quand un objet doit être considéré comme "inférieur à" ou "supérieur à".

Supposons que vous ayez un tas d'entiers et que vous vouliez les trier. C'est assez facile, mettez-les simplement dans une collection triée, non?

TreeSet<Integer> m = new TreeSet<Integer>(); 
m.add(1);
m.add(3);
m.add(2);
for (Integer i : m)
... // values will be sorted

Mais maintenant supposons que j'ai un objet personnalisé, où le tri a un sens pour moi, mais n'est pas défini. Disons que j'ai des données représentant les districts par code postal avec la densité de population, et je veux les trier par densité:

public class District {
  String zipcode; 
  Double populationDensity;
}

Maintenant, le moyen le plus simple de les trier est de les définir avec un ordre naturel en implémentant Comparable, ce qui signifie qu'il existe une manière standard de définir ces objets pour être ordonnés:

public class District implements Comparable<District>{
  String zipcode; 
  Double populationDensity;
  public int compareTo(District other)
  {
    return populationDensity.compareTo(other.populationDensity);
  }
}

Notez que vous pouvez faire l'équivalent en définissant un comparateur. La différence est que le comparateur définit la logique de commande en dehors de l'objet . Peut-être que dans un processus séparé, j'ai besoin de commander les mêmes objets par code postal - dans ce cas, l'ordre n'est pas nécessairement une propriété de l'objet, ou diffère de l'ordre naturel des objets. Vous pouvez utiliser un comparateur externe pour définir un ordre personnalisé sur les entiers, par exemple en les triant par leur valeur alphabétique.

Fondamentalement, la logique de commande doit exister quelque part. Cela peut être -

  • dans l'objet lui-même, s'il est naturellement comparable (étend les entiers comparables -eg)

  • fourni dans un comparateur externe, comme dans l'exemple ci-dessus.

Steve B.
la source
bon exemple, mais il doit être à la TreeSet<Integer>place de TreeMap<Integer>, car ce dernier n'existe pas, les TreeMaps sont toujours des <Key,Value>paires. Btw, une hypothétique TreeMap<District, Object>ne fonctionnerait que si le district mettait en œuvre Comparable, non? J'essaie toujours de comprendre cela
phil294
14

Cité du javadoc;

Cette interface impose un ordre total sur les objets de chaque classe qui l'implémente. Cet ordre est appelé ordre naturel de la classe, et la méthode compareTo de la classe est appelée sa méthode de comparaison naturelle.

Les listes (et tableaux) d'objets qui implémentent cette interface peuvent être triés automatiquement par Collections.sort (et Arrays.sort). Les objets qui implémentent cette interface peuvent être utilisés comme clés dans une carte triée ou comme éléments dans un ensemble trié, sans qu'il soit nécessaire de spécifier un comparateur.

Edit: ..et a rendu le bit important en gras.

Qwerky
la source
4
Je dirais que la phrase après celle que vous avez mise en gras est tout aussi importante (sinon plus).
Michael Borgwardt
8

Le fait qu'une classe implémente Comparablesignifie que vous pouvez prendre deux objets de cette classe et les comparer. Certaines classes, comme certaines collections (fonction de tri dans une collection) qui maintiennent les objets dans l'ordre, reposent sur leur comparaison (pour trier, vous devez savoir quel objet est le «plus gros» et ainsi de suite).

Amir Rachum
la source
8

La plupart des exemples ci-dessus montrent comment réutiliser un objet comparable existant dans la fonction compareTo. Si vous souhaitez implémenter votre propre compareTo lorsque vous souhaitez comparer deux objets de la même classe, dites un objet AirlineTicket que vous souhaitez trier par prix (moins est classé en premier), suivi du nombre d'escales (encore une fois, moins est classé premier), vous feriez ce qui suit:

class AirlineTicket implements Comparable<Cost>
{
    public double cost;
    public int stopovers;
    public AirlineTicket(double cost, int stopovers)
    {
        this.cost = cost; this.stopovers = stopovers ;
    }

    public int compareTo(Cost o)
    {
        if(this.cost != o.cost)
          return Double.compare(this.cost, o.cost); //sorting in ascending order. 
        if(this.stopovers != o.stopovers)
          return this.stopovers - o.stopovers; //again, ascending but swap the two if you want descending
        return 0;            
    }
}
Arviman
la source
6

Un moyen simple d'implémenter des comparaisons de champs multiples est d' utiliser ComparisonChain de Guava - alors vous pouvez dire

   public int compareTo(Foo that) {
     return ComparisonChain.start()
         .compare(lastName, that.lastName)
         .compare(firstName, that.firstName)
         .compare(zipCode, that.zipCode)
         .result();
   }

au lieu de

  public int compareTo(Person other) {
    int cmp = lastName.compareTo(other.lastName);
    if (cmp != 0) {
      return cmp;
    }
    cmp = firstName.compareTo(other.firstName);
    if (cmp != 0) {
      return cmp;
    }
    return Integer.compare(zipCode, other.zipCode);
  }
}
Ran Adler
la source
3

Par exemple, lorsque vous souhaitez avoir une collection ou une carte triée

Fernando Miguélez
la source
Ce que Fernando veut dire, c'est: si vous stockez des "choses" qui implémentent Comparable dans une classe de conteneur triée, la classe de conteneur triée peut automatiquement ordonner ces "choses".
Ian Durkan
2

Comparable est utilisé pour comparer les instances de votre classe. Nous pouvons comparer des instances de plusieurs façons, c'est pourquoi nous devons implémenter une méthode compareToafin de savoir comment (attributs) nous voulons comparer les instances.

Dog classe:

package test;
import java.util.Arrays;

public class Main {

    public static void main(String[] args) {
        Dog d1 = new Dog("brutus");
        Dog d2 = new Dog("medor");
        Dog d3 = new Dog("ara");
        Dog[] dogs = new Dog[3];
        dogs[0] = d1;
        dogs[1] = d2;
        dogs[2] = d3;

        for (int i = 0; i < 3; i++) {
            System.out.println(dogs[i].getName());
        }
        /**
         * Output:
         * brutus
         * medor
         * ara
         */

        Arrays.sort(dogs, Dog.NameComparator);
        for (int i = 0; i < 3; i++) {
            System.out.println(dogs[i].getName());
        }
        /**
         * Output:
         * ara
         * medor
         * brutus
         */

    }
}

Main classe:

package test;

import java.util.Arrays;

public class Main {

    public static void main(String[] args) {
        Dog d1 = new Dog("brutus");
        Dog d2 = new Dog("medor");
        Dog d3 = new Dog("ara");
        Dog[] dogs = new Dog[3];
        dogs[0] = d1;
        dogs[1] = d2;
        dogs[2] = d3;

        for (int i = 0; i < 3; i++) {
            System.out.println(dogs[i].getName());
        }
        /**
         * Output:
         * brutus
         * medor
         * ara
         */

        Arrays.sort(dogs, Dog.NameComparator);
        for (int i = 0; i < 3; i++) {
            System.out.println(dogs[i].getName());
        }
        /**
         * Output:
         * ara
         * medor
         * brutus
         */

    }
}

Voici un bon exemple comment utiliser comparable en Java:

http://www.onjava.com/pub/a/onjava/2003/03/12/java_comp.html?page=2

non non
la source
2

Lorsque vous implémentez l' Comparableinterface, vous devez implémenter la méthode compareTo(). Vous en avez besoin pour comparer des objets, afin d'utiliser, par exemple, la méthode de tri de ArrayListclasse. Vous avez besoin d'un moyen de comparer vos objets pour pouvoir les trier. Vous avez donc besoin d'une compareTo()méthode personnalisée dans votre classe pour pouvoir l'utiliser avec la ArrayListméthode sort. La compareTo()méthode renvoie -1,0,1.

Je viens de lire un chapitre correspondant dans Java Head 2.0, j'apprends encore.

lxknvlk
la source
1

OK, mais pourquoi ne pas simplement définir une compareTo()méthode sans implémenter une interface comparable. Par exemple, une classe Citydéfinie par ses nameet temperatureet

public int compareTo(City theOther)
{
    if (this.temperature < theOther.temperature)
        return -1;
    else if (this.temperature > theOther.temperature)
        return 1;
    else
        return 0;
}
Ale B
la source
Cela ne fonctionne pas. Sans implémenter comparable - Je reçois une exception de diffusion de classe
Karan Ahuja