Warum sollte eine Java-Klasse vergleichbar implementieren?


Antworten:


212

Hier ist ein Beispiel aus dem wirklichen Leben. Beachten Sie, dass Stringauch implementiert 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;
    }
}

später..

/**
 * 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);
}

15
Ich möchte nur darauf hinweisen, dass Sie häufig überschreiben möchten equals(und daher hashCode), um mit Ihrer compareToMethode übereinzustimmen. Dies ist beispielsweise erforderlich, wenn die Klasse mit a gut spielen soll TreeSet.
Pidge

Warum nicht einfach zurückkehren last?
Anirban Nag 'tintinmj'

@ AnirbanNag'tintinmj ', um automatisch nach dem Vornamen zu sortieren, falls der Nachname identisch ist.
OddDev

Plus eins für die gute Erklärung, warum compareTo ein int zurückgibt und was es bedeutet. Am hilfreichsten.
James.garriss

1
@ user3932000: Richtig, das ist so ziemlich der Punkt aller Schnittstellen. Beachten Sie jedoch, dass "andere Java-Methoden" vom Benutzer geschriebene Methoden enthalten! Tatsächlich würde ich behaupten, dass die meisten Schnittstellen vom Code des Benutzers verwendet werden. In größeren Codebasen wird "Sie selbst" schnell zu "Anderen"
Enno Shioji

40

Vergleichbar definiert eine natürliche Ordnung. Dies bedeutet, dass Sie definieren, wann ein Objekt als "kleiner als" oder "größer als" betrachtet werden soll.

Angenommen, Sie haben eine Reihe von Ganzzahlen und möchten diese sortieren. Das ist ziemlich einfach, legen Sie sie einfach in eine sortierte Sammlung, oder?

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

Angenommen, ich habe ein benutzerdefiniertes Objekt, bei dem das Sortieren für mich sinnvoll, aber nicht definiert ist. Angenommen, ich habe Daten, die Bezirke nach Postleitzahl mit Bevölkerungsdichte darstellen, und ich möchte sie nach Dichte sortieren:

public class District {
  String zipcode; 
  Double populationDensity;
}

Der einfachste Weg, sie zu sortieren, besteht darin, sie mit einer natürlichen Reihenfolge zu definieren, indem Comparable implementiert wird. Dies bedeutet, dass diese Objekte standardmäßig so angeordnet sind, dass sie geordnet werden:

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

Beachten Sie, dass Sie das Gleiche tun können, indem Sie einen Komparator definieren. Der Unterschied besteht darin, dass der Komparator die Ordnungslogik außerhalb des Objekts definiert . Vielleicht muss ich in einem separaten Prozess dieselben Objekte nach Postleitzahl bestellen - in diesem Fall ist die Reihenfolge nicht unbedingt eine Eigenschaft des Objekts oder unterscheidet sich von der natürlichen Reihenfolge der Objekte. Sie können einen externen Komparator verwenden, um eine benutzerdefinierte Reihenfolge für Ganzzahlen zu definieren, indem Sie sie beispielsweise nach ihrem alphabetischen Wert sortieren.

Grundsätzlich muss die Ordnungslogik irgendwo existieren. Das kann sein -

  • im Objekt selbst, wenn es natürlich vergleichbar ist (erweitert Comparable -eg Ganzzahlen)

  • Lieferung in einem externen Komparator, wie im obigen Beispiel.


gutes Beispiel, aber es muss TreeSet<Integer>stattdessen sein TreeMap<Integer>, dass TreeMaps immer <Key,Value>-paare sind, da letzteres nicht existiert . Übrigens TreeMap<District, Object>würde eine Hypothese nur funktionieren, wenn District Comparable implementiert hätte, oder?
Ich

14

Zitiert aus dem Javadoc;

Diese Schnittstelle legt den Objekten jeder Klasse, die sie implementiert, eine Gesamtreihenfolge auf. Diese Reihenfolge wird als natürliche Reihenfolge der Klasse bezeichnet, und die compareTo-Methode der Klasse wird als natürliche Vergleichsmethode bezeichnet.

Listen (und Arrays) von Objekten, die diese Schnittstelle implementieren, können automatisch nach Collections.sort (und Arrays.sort) sortiert werden. Objekte, die diese Schnittstelle implementieren, können als Schlüssel in einer sortierten Zuordnung oder als Elemente in einer sortierten Menge verwendet werden, ohne dass ein Komparator angegeben werden muss.

Edit: ..und machte das wichtige Stück fett.


4
Ich würde sagen, dass der Satz nach dem, den Sie fett gedruckt haben, genauso wichtig ist (wenn nicht sogar wichtiger).
Michael Borgwardt

8

Die Tatsache, dass eine Klasse implementiert, Comparablebedeutet, dass Sie zwei Objekte aus dieser Klasse nehmen und vergleichen können. Einige Klassen, wie bestimmte Sammlungen (Sortierfunktion in einer Sammlung), die Objekte in Ordnung halten, setzen voraus, dass sie vergleichbar sind (um zu sortieren, müssen Sie wissen, welches Objekt das "größte" ist und so weiter).


8

Die meisten der obigen Beispiele zeigen, wie ein vorhandenes vergleichbares Objekt in der Funktion compareTo wiederverwendet wird. Wenn Sie Ihren eigenen compareTo implementieren möchten, wenn Sie zwei Objekte derselben Klasse vergleichen möchten, sagen Sie ein AirlineTicket-Objekt, das Sie nach Preis sortieren möchten (weniger steht an erster Stelle), gefolgt von der Anzahl der Zwischenstopps (wiederum ist weniger) Rang 1), würden Sie Folgendes tun:

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;            
    }
}

6

Eine einfache Möglichkeit, mehrere Feldvergleiche zu implementieren, ist Guavas ComparisonChain - dann können Sie sagen

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

anstatt

  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);
  }
}

3

Zum Beispiel, wenn Sie eine sortierte Sammlung oder Karte haben möchten


Fernando bedeutet: Wenn Sie "Dinge", die Comparable implementieren, in einer sortierten Containerklasse speichern, kann die sortierte Containerklasse diese "Dinge" automatisch bestellen.
Ian Durkan

2

Vergleichbar wird verwendet, um Instanzen Ihrer Klasse zu vergleichen. Wir können Instanzen auf viele Arten vergleichen, deshalb müssen wir eine Methode implementieren, compareToum zu wissen, wie (Attribute) wir Instanzen vergleichen möchten.

Dog Klasse:

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 Klasse:

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
         */

    }
}

Hier ist ein gutes Beispiel für die Verwendung von vergleichbaren in Java:

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


2

Wenn Sie die ComparableSchnittstelle implementieren , müssen Sie die Methode implementieren compareTo(). Sie benötigen es, um Objekte zu vergleichen, um beispielsweise die Sortiermethode der ArrayListKlasse zu verwenden. Sie benötigen eine Möglichkeit, Ihre Objekte zu vergleichen, um sie sortieren zu können. Sie benötigen also eine benutzerdefinierte compareTo()Methode in Ihrer Klasse, damit Sie sie mit der ArrayListSortiermethode verwenden können. Die compareTo()Methode gibt -1,0,1 zurück.

Ich habe gerade ein entsprechendes Kapitel in Java Head 2.0 gelesen und lerne noch.


1

OK, aber warum nicht einfach eine compareTo()Methode definieren , ohne eine vergleichbare Schnittstelle zu implementieren? Zum Beispiel eine Klasse, Citydie durch ihre nameund temperatureund definiert ist

public int compareTo(City theOther)
{
    if (this.temperature < theOther.temperature)
        return -1;
    else if (this.temperature > theOther.temperature)
        return 1;
    else
        return 0;
}

Das funktioniert nicht. Ohne vergleichbare zu implementieren - ich bekomme Classcast-Ausnahme
Karan Ahuja
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.