Dlaczego klasa Java powinna implementować porównywalne?


Odpowiedzi:


212

Oto próbka z prawdziwego życia. Zauważ, że Stringrównież implementuje 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;
    }
}

później..

/**
 * 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
Chcę tylko zauważyć, że często będziesz chciał przesłonić equals(a zatem hashCode) zachować spójność ze swoją compareTometodą. Na przykład jest to konieczne, jeśli chcesz, aby klasa dobrze grała z rozszerzeniem TreeSet.
pidge

Dlaczego po prostu nie wrócić last?
Anirban Nag 'tintinmj'

@ AnirbanNag'tintinmj ', aby sortować automatycznie według imienia w przypadku, gdy nazwisko jest takie samo.
OddDev

Plus jeden za dobre wyjaśnienie, dlaczego compareTo zwraca int i co to znaczy. Najbardziej pomocny.
james.garriss

1
@ user3932000: Racja, o to właściwie chodzi w przypadku wszystkich interfejsów. Należy jednak pamiętać, że „inne metody Java” obejmują metody napisane przez użytkownika! W rzeczywistości twierdzę, że większość interfejsów jest wykorzystywana przez kod użytkownika. W większych bazach kodu „siebie” szybko staje się „innymi”
Enno Shioji

40

Porównywalne definiuje naturalne uporządkowanie. Oznacza to, że definiujesz go, gdy jeden obiekt powinien być uważany za „mniejszy niż” lub „większy niż”.

Załóżmy, że masz kilka liczb całkowitych i chcesz je posortować. To całkiem proste, po prostu umieść je w posortowanej kolekcji, prawda?

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

Ale teraz załóżmy, że mam jakiś niestandardowy obiekt, dla którego sortowanie ma dla mnie sens, ale jest niezdefiniowane. Powiedzmy, że mam dane reprezentujące dzielnice według kodu pocztowego z gęstością zaludnienia i chcę posortować je według gęstości:

public class District {
  String zipcode; 
  Double populationDensity;
}

Teraz najłatwiejszym sposobem ich sortowania jest zdefiniowanie ich za pomocą naturalnego uporządkowania poprzez zaimplementowanie Comparable, co oznacza, że ​​istnieje standardowy sposób definiowania tych obiektów w celu ich uporządkowania .:

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

Zauważ, że możesz zrobić to samo, definiując komparator. Różnica polega na tym, że komparator definiuje logikę porządkowania poza obiektem . Może w osobnym procesie muszę uporządkować te same obiekty według kodu pocztowego - w takim przypadku kolejność niekoniecznie jest własnością obiektu lub różni się od naturalnego uporządkowania obiektów. Możesz użyć zewnętrznego komparatora, aby zdefiniować niestandardową kolejność liczb całkowitych, na przykład sortując je według wartości alfabetycznej.

Zasadniczo logika porządkowania musi gdzieś istnieć. To może być -

  • w samym obiekcie, jeśli jest naturalnie porównywalny (rozciąga liczby całkowite Comparable -eg)

  • dostarczane w zewnętrznym komparatorze, jak w powyższym przykładzie.


dobry przykład, ale musi być TreeSet<Integer>zamiast tego TreeMap<Integer>, ponieważ to drugie nie istnieje, mapy drzew są zawsze <Key,Value>-parami. Przy okazji, hipoteza TreeMap<District, Object>działałaby tylko wtedy, gdyby District wdrożyło Porównywalny, prawda? Wciąż próbuję to zrozumieć
phil294

14

Cytowane z javadoc;

Ten interfejs narzuca całkowite uporządkowanie obiektów każdej klasy, która go implementuje. To uporządkowanie jest określane jako naturalne uporządkowanie klasy, a metoda compareTo klasy jest nazywana jej naturalną metodą porównania.

Listy (i tablice) obiektów, które implementują ten interfejs, mogą być sortowane automatycznie według Collections.sort (i Arrays.sort). Obiekty, które implementują ten interfejs, mogą być używane jako klucze w posortowanej mapie lub jako elementy w posortowanym zestawie, bez konieczności określania komparatora.

Edytuj: ... i pogrubiłem ważny bit.


4
Powiedziałbym, że zdanie po tym, które pogrubiłeś, jest równie (jeśli nie ważniejsze) ważne.
Michael Borgwardt

8

Fakt, że klasa implementuje, Comparableoznacza, że ​​możesz pobrać dwa obiekty z tej klasy i porównać je. Niektóre klasy, takie jak pewne kolekcje (funkcja sortowania w kolekcji), które utrzymują obiekty w porządku, polegają na tym, że są porównywalne (aby posortować, musisz wiedzieć, który obiekt jest „największy” i tak dalej).


8

Większość powyższych przykładów pokazuje, jak ponownie użyć istniejącego porównywalnego obiektu w funkcji compareTo. Jeśli chcesz zaimplementować własne porównanie Aby porównać dwa obiekty tej samej klasy, powiedz obiekt AirlineTicket, który chcesz posortować według ceny (na pierwszym miejscu jest mniej), a następnie liczba międzylądowań (ponownie, mniej to na pierwszym miejscu), wykonasz następujące czynności:

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

Łatwym sposobem na zaimplementowanie wielu porównań pól jest porównanie łańcucha Guava - wtedy możesz powiedzieć

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

zamiast

  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

Na przykład, gdy chcesz mieć posortowaną kolekcję lub mapę


Fernando ma na myśli: jeśli przechowujesz „rzeczy”, które implementują Comparable w posortowanej klasie kontenera, posortowana klasa kontenera może automatycznie uporządkować te „rzeczy”.
Ian Durkan,

2

Porównywalny służy do porównywania wystąpień Twojej klasy. Możemy porównywać instancje na wiele sposobów, dlatego musimy zaimplementować metodę compareTo, aby wiedzieć, jak (atrybuty) chcemy porównać instancje.

Dog klasa:

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

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

    }
}

Oto dobry przykład, jak używać porównywalnego w Javie:

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


2

Kiedy implementujesz Comparableinterfejs, musisz zaimplementować metodę compareTo(). Potrzebujesz go do porównywania obiektów, aby np. Skorzystać z metody sortowania ArrayListklasy. Potrzebujesz sposobu na porównanie obiektów, aby móc je sortować. Potrzebujesz więc niestandardowej compareTo()metody w swojej klasie, aby móc jej używać z ArrayListmetodą sort. Te compareTo()powroty metoda -1,0,1.

Właśnie przeczytałem odpowiedni rozdział w Java Head 2.0, wciąż się uczę.


1

OK, ale dlaczego po prostu nie zdefiniować compareTo()metody bez implementacji porównywalnego interfejsu. Na przykład klasa Cityzdefiniowana przez jego namei temperaturei

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

To nie działa. Bez implementacji porównywalnego - dostaję wyjątek od klasowego
Karan Ahuja
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.