D) Objekte sortieren – Comparator und Comparable

Oftmals kommt es vor, dass Sie verschiedene Objekte einer Klasse sortieren müssen. Natürlich können Sie hierzu einen geeigneten Sortieralgorithmus wie den Quicksort oder den Mergesort selbst implementieren. Aber warum sich die Mühe machen? Java bietet Ihnen die Möglichkeit eine java.util.List oder ein Array zu sortieren. Wie das funktioniert erfahren Sie in diesem Kapitel.

Collections.sort bzw. Arrays.sort

Um diese Sortierfunktionen zu verwenden (es wird ein modifizierter Mergesort verwendet), bieten die Klassen java.util.Collections (Sortierung einer List) und java.util.Arrays (Sortierung von Arrays) jeweils die Methode sort mit unterschiedlichen Parametern an. Mit der Klasse Arrays können Sie ein Array eines primitiven Datentyps, ein Array von Objekten einer beliebigen Klasse, oder einen generischen Typen sortieren lassen (jeweils optional mit der Angabe von wo bis wann sortiert werden soll). Beim Array des generischen Typs kann als weitere Möglichkeit noch ein java.util.Comparator übergeben werden. Was das ist, erfahren Sie später in diesem Kapitel. Collections.sort sortiert eine generische java.util.List – ebenfalls optional mit einem geeigneten java.util.Comparator.

Sehen Sie sich ein Beispiel an:

package de.jbb.cuc;

import java.util.Arrays;

public class ArraysAndCollectionsTest {

  public static void main(String[] args) {

    int[] unsorted = { 5, 6, 1, 123, 543, 32, 53, 75, 8 };
    System.out.println("Unsortiert:");
    System.out.println("-----------");
    for (int i : unsorted) {
      System.out.println(i);
    }
    Arrays.sort(unsorted);
    System.out.println("Sortiert:");
    System.out.println("-----------");
    for (int i : unsorted) {
      System.out.println(i);
    }
  }
}
package de.jbb.cuc;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ArraysAndCollectionsTest {

  public static void main(String[] args) {

    List<String> list = new ArrayList<String>(8);
    list.add("Sebastian Würkner");
    list.add("Stefan Kiesel");
    list.add("Andreas Pries");
    list.add("Byte-Welt.de");
    list.add("Erich Gamma");
    list.add("Richard Helm");
    list.add("Ralph Johnson");
    list.add("John Vlissides");
    System.out.println("Unsortiert:");
    System.out.println("-----------");
    for (String str : list) {
      System.out.println(str);
    }
    Collections.sort(list);
    System.out.println("Sortiert:");
    System.out.println("-----------");
    for (String str : list) {
      System.out.println(str);
    }
  }
}

Sie werden feststellen, dass die Sortierung in Java denkbar einfach ist – bis jetzt.

Eigene Objekte sortieren

Komplizierter wird es allerdings, wenn Sie eigene Objekte sortieren wollen. Denn woher soll Java wissen, anhand welcher Eigenschaft Ihre Objekte sortiert werden sollen? Ein Beispiel:

Sie möchten Objekte der (sehr einfach gehaltenen) Klasse Bier sortieren:

package de.jbb.cuc;

public class Bier {

  private String name;
  private String herkunft;
  private float inhalt;

  public Bier() {}

  public Bier(String name, String herkunft, float inhalt) {

    this.name = name;
    this.herkunft = herkunft;
    this.inhalt = inhalt;
  }

  public String getName() {
    return this.name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getHerkunft() {
    return this.herkunft;
  }

  public void setHerkunft(String herkunft) {
    this.herkunft = herkunft;
  }

  public float getInhalt() {
    return this.inhalt;
  }

  public void setInhalt(float inhalt) {
    this.inhalt = inhalt;
  }

  public String toString() {
    return this.name + " aus " + this.herkunft + " mit " + this.inhalt + " Litern";
  }
}

Lassen Sie sich nun zu Testzwecken ein Array oder eine Liste von Objekten dieser Klasse sortieren:

package de.jbb.cuc;

import java.util.Arrays;

public class ArraysAndCollectionsTest {

  public static void main(String[] args) {

    Bier[] biere = { 
        new Bier("Würzburger Hofbräu", "Würzburg", 0.5F), 
        new Bier("Becks", "Bremen", 0.33F), 
        new Bier("Paulaner", "München", 0.5F), 
        new Bier("Distelhäuser", "Distelhausen", 0.33F) 
    };
    System.out.println("Unsortiert:");
    System.out.println("-----------");
    for (Bier bier : biere) {
      System.out.println(bier);
    }
    Arrays.sort(biere);
    System.out.println("Sortiert:");
    System.out.println("-----------");
    for (Bier bier : biere) {
      System.out.println(bier);
    }
  }
}

Sie werden beim Ausführen (oder bei der Verwendung einer java.util.List bei der Kompilierung) feststellen, dass es zu einem Fehler kommt.

Exception in thread "main" java.lang.ClassCastException: de.jbb.cuc.Bier cannot be cast to java.lang.Comparable
	at java.util.Arrays.mergeSort(Unknown Source)
	at java.util.Arrays.sort(Unknown Source)

Diese Exception resultiert daraus, dass Java nicht weiß, nach was es die Klasse Bier sortieren soll. Um das festzulegen, können Sie bspw. das Interface Comparable verwenden.

Comparable

Um der sort-Methode zu sagen, nach was sortiert werden soll, lassen Sie die zu sortierende Klasse das Interface Comparable implementieren. Dieses Interface schreibt vor, dass die Methode public int compareTo(T o) überschrieben wird. Es ist Ihre Aufgabe in der Methode das aktuelle Objekt mit dem übergebenen Objekt zu vergleichen. Sollte das übergebene Objekt untergeordnet werden, geben Sie einen negativen Wert zurück, sollten die Objekte gleichwertig sein, geben Sie 0 zurück, ansonsten einen positiven Wert. Eine Beispielimplementation in unserer Bier-Klasse um nach dem Namen des Bieres zu sortieren könnte so aussehen (String implementiert auch Comparable, so dass wir uns einfach dessen compareTo-Methode bedienen können):

...
public class Bier implements Comparable<Bier> {

  ...

  @Override
  public int compareTo(Bier b) {
    if (b.getName() == null && this.getName() == null) {
      return 0;
    }
    if (this.getName() == null) {
      return 1;
    }
    if (b.getName() == null) {
      return -1;
    }
    return this.getName().compareTo(b.getName());
  }
}

Für den Fall, dass b gleich null ist, sollte eine NullPointerException geworfen werden. Jetzt können Sie das Array oder die Liste sortieren lassen.

Comparator

Eine Alternative zu Comparable ist der java.util.Comparator. Der Aufbau eines Comparators ist dem von Comparable sehr ähnlich. Der Unterschied besteht darin, dass ein Comparator nicht von der zu vergleichenden Klasse implementiert werden muss. Dadurch können Sie auch einfach Objekte von Klassen vergleichen, die Sie nicht selbst geschrieben haben oder verändern dürfen. Als Konsequenz werden der compare-Methode (der Comparator verwendet zum Sortieren die Methode compare anstelle von compareTo) gleich beide zu vergleichenden Objekte übergeben.

Als Konsequenz kann eine ClassCastException geworfen werden, falls keine generischen Typen festgelegt werden und beide Klassen nicht kompatibel zueinander sind. Bzgl. der Reihenfolge nimmt das zuerst übergebene Argument die Position von this bei der Verwendung von Comparable ein. Im Klartext bedeutet dies:

negativer Rückgabewert: Der erste Parameter ist untergeordnet
0 als Rückgabewert: Beide Parameter werden gleich eingeordnet
positiver Rückgabewert: Der erste Parameter ist übergeordnet

Sehen Sie sich nun das obige Beispiel als Comparator an.

package de.jbb.cuc;

import java.util.Comparator;

public class BierNameComparator implements Comparator<Bier> {

  @Override
  public int compare(Bier b1, Bier b2) {
    if (b1.getName() == null && b2.getName() == null) {
      return 0;
    }
    if (b1.getName() == null) {
      return 1;
    }
    if (b2.getName() == null) {
      return -1;
    }
    return b1.getName().compareTo(b2.getName());
  }
}
package de.jbb.cuc;

import java.util.Arrays;
import java.util.Comparator;

public class ArraysAndCollectionsTest {

  public static void main(String[] args) {

    Comparator<Bier> comp = new BierNameComparator();
    Bier[] biere = { 

        new Bier("Würzburger Hofbräu", "Würzburg", 0.5F), 
        new Bier("Becks", "Bremen", 0.33F), 
        new Bier("Paulaner", "München", 0.5F), 
        new Bier("Distelhäuser", "Distelhausen", 0.33F) };
    System.out.println("Unsortiert:");
    System.out.println("-----------");
    for (Bier bier : biere) {
      System.out.println(bier);
    }
    Arrays.sort(biere, comp);
    System.out.println("Sortiert:");
    System.out.println("-----------");
    for (Bier bier : biere) {
      System.out.println(bier);
    }
  }
}

Der Vorteil eines Comparators liegt darin, dass das Kriterium, nach welchem sortiert werden soll, einfach ausgetauscht werden kann. Schreiben Sie sich einfach einen weiteren Comparator, der bspw. zuerst nach Inhalt, dann nach Herkunft und letztendlich nach Namen sortiert (auf die Überprüfung, ob der Name oder die Herkunft gleich null ist, wird an dieser Stelle verzichtet):

package de.jbb.cuc;

import java.util.Comparator;

public class BierComparator implements Comparator<Bier> {

  @Override
  public int compare(Bier b1, Bier b2) {

    if (b1.getInhalt() == b2.getInhalt()) {
      if (b1.getHerkunft().compareTo(b2.getHerkunft()) == 0) {
        return b1.getName().compareTo(b2.getName());
      }
      else {
        return b1.getHerkunft().compareTo(b2.getHerkunft());
      }
    }
    else if (b1.getInhalt() > b2.getInhalt()) {
      return -1;
    }
    else {
      return 1;
    }
  }
}

Jetzt genügt es in Ihrer Main-Methode den BierNameComparator durch einen BierComparator auszutauschen und schon wird nach den neuen Kriterien geordnet.

package de.jbb.cuc;

import java.util.Arrays;
import java.util.Comparator;

public class ArraysAndCollectionsTest {

  public static void main(String[] args) {

    Comparator<Bier> comp = new BierComparator();
    Bier[] biere = { 
        new Bier("Würzburger Hofbräu", "Würzburg", 0.5F), 
        new Bier("Becks", "Bremen", 0.33F), 
        new Bier("Paulaner", "München", 0.5F), 
        new Bier("Distelhäuser", "Distelhausen", 0.33F) };
    System.out.println("Unsortiert:");
    System.out.println("-----------");
    for (Bier bier : biere) {
      System.out.println(bier);
    }
    Arrays.sort(biere, comp);
    System.out.println("Sortiert:");
    System.out.println("-----------");
    for (Bier bier : biere) {
      System.out.println(bier);
    }
  }
}

25 Replies to “D) Objekte sortieren – Comparator und Comparable”

  1. Markus

    Kleine Ergänzung zu diesem super Artikel.

    So kann die Biere auch absteigend sortieren.

    import java.util.*;
    
    
    public class sort {
    	
    
    	  public static void main(String[] args) {
    
    		List biere = new ArrayList();
    
    		biere.add(new Bier("Würzburger Hofbräu", "Würzburg", 0.5F));
    		biere.add(new Bier("Becks", "Bremen", 0.33F));
    		biere.add(new Bier("Paulaner", "München", 0.5F));
    		biere.add(new Bier("Distelhäuser", "Distelhausen", 0.33F));
    
    		System.out.println("Unsortiert:");
    		System.out.println("-----------");
    		for (Bier bier : biere) {
    			System.out.println(bier);
    		}
    		Collections.sort(biere);
    		System.out.println("Sortiert:");
    		System.out.println("-----------");
    		for (Bier bier : biere) {
    			System.out.println(bier);
    		}
    
    		Comparator reverse = Collections.reverseOrder();
    		Collections.sort(biere, reverse);
    		System.out.println("REEEVERSE:");
    		System.out.println("-----------");
    		for (Bier bier : biere) {
    			System.out.println(bier);
    		}
    	}  
    }
  2. Ralph

    Hallo,

    ich glaube, die NULL-Behandlung in den compare und compareTo Methoden ist fehlerhaft – bzw. wiedersprechen dem Contract der Interfaces Comparable bzw. Comparator.

    Zumindest für die Comparable#compareTo Methode ist die JavaDoc eindeuting:
    „Note that null is not an instance of any class, and e.compareTo(null) should throw a NullPointerException even though e.equals(null) returns false.“ (JavaDoc zum Interface Comparable 3. Absatz).

    Bei der Comparator#compare Methode bin ich mir nicht sicher – da steht nichts direkt zu NULL in der Dokumentation außer: „@throws ClassCastException – if the specified object’s type prevents it from being compared to this Object.“
    Zusammen mit der Aussage, dass NULL keine Instanz einer Klasse ist, müsste das meiner Meinung nach bedeuten, dass in diesem Fall eine ClassCastException geworfen wird. – (Ich würste gern wie andere darüber denken.)

  3. Stefan Kiesel

    Hallo Ralph!

    Stimmt, da muss ich zustimmen. Ich werde den Artikel umgehend ausbessern.

    "Test".compareTo(null); // => NullPointerException
    Collator.getInstance().compare(new Integer(21), "Test"); // => ClassCastException
    Collator.getInstance().compare(null, "Test"); // NullPointerException
    Collator.getInstance().compare("Test", null); // NullPointerException
    Collator.getInstance().compare(null, null); // NullPointerException

    Vielen Dank für den Hinweis.

    Gruß
    Stefan

  4. Marian

    Vielen Danke für die super Erklärung. Habe keine bessere gefunden und kenn mich jetzt zum ersten mal mit den Comporators aus!

  5. ricky

    wie kann man herausfinden, was die Methode toString(int[])der Klasse Arrays des Packages java.util macht?

  6. Thomas B
    public class sortieren 
    {
    public static void main (String args [])
    	{
    	int i,j;
    	for (i= 0; i < args.length; i++)
    		{
    		for (j= i+ 1; j 0)
    				{
    				String temp = args [i];
    				args [i] = args [j];
    				args [j] = temp;
    				}
    			}
    		}
    	System.out.println(args [0]+ " " + args [1] + " " +args[2]+ " " + args [3]);
    	}
    }
    
    Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
    	at sortieren.main(sortieren.java:18)

    Finde einfach den Fehler nicht.
    Das Programm Sortiert Namen

  7. Stefan Kiesel

    Hallo Thomas B,

    die Frage hat ja eigentlich recht wenig mit dem Thema zu tun, trotzdem:

    scheinbar haben Sie beim Ausführen des Programms keine Parameter übergeben, weshalb die Länge des args-Arrays 0 ist. Somit können Sie im System.out.println auch nicht auf die Werte des Parameters zugreifen.

    Beste Grüße
    Stefan Kiesel

  8. Thomas B

    Danke für die antwort. Kennt sich zufällig jemand mit Eclips aus ?
    Muss ja nen befehl geben das ich die sachen Deklarieren kann.

    Schon mal vielen Dank

  9. Stefan Kiesel

    Das hat nichts mit Eclipse zu tun, die Parameter müssen einfach nur übergeben werden. Kann man in Eclipse u. a. direkt im Run-Dialog machen. Aber da die Frage wie gesagt nichts mit dem eigentlichen Thema zu tun hat, wäre es wünschenswert (und vermutlich auch effektiver/hilfreicher) in einem entsprechenden Java-Forum zu fragen.

    Grüße
    Stefan

  10. Stefan Kiesel

    Hallo Hans Peter,

    einfach beim vorletztem Coding den Inhalt der ersten if-Anweisung

        if (b1.getInhalt() == b2.getInhalt()) {
          if (b1.getHerkunft().compareTo(b2.getHerkunft()) == 0) {
            return b1.getName().compareTo(b2.getName());
          }
          else {
            return b1.getHerkunft().compareTo(b2.getHerkunft());
          }
        }

    durch return 0; ersetzen:

        if (b1.getInhalt() == b2.getInhalt()) {
          return 0;
        }

    Grüße
    Stefan

  11. noenglish

    Ein Java Anfänger:
    Ich schaffe es nicht einen Comparator zu erstellen, der nach zwei (oder mehr) String-Attributen sortiert. Im obigen Beispiel also nach .getName und dann nach getHerkunft.
    Bei allen Beispielen die ich gefunden habe werden nie zwei String-Attribute verwendet.
    Für ein Codebeispiel wäre ich sehr dankbar.

    Vielen Dank
    noenglish

  12. Stefan Kiesel

    Hallo noenglish,

    im Artikel ist bereits ein Beispiel. Der BierComparator sortiert sogar nach drei Attributen insgesamt, wovon zwei String-Attribute sind. Für einen Comparator, der nur nach diesen beiden Kriterien sortiert, müsste man einfach die Zeilen 10 und 17-23 auskommentieren. Dabei wird zuerst nach Herkunft und anschließend nach Name sortiert.

    Sicherlich könnte man das Verfahren noch geringfügig optimieren, indem man sich das Ergebnis des ersten compare-Vergleichs der Herkunft zwischenspeichert, anstatt den Aufruf zweimal auszuführen.

    Ich hoffe das ist jetzt klarer?!

    Grüße
    Stefan

  13. noenglish

    Guten Morgen Stefan,
    vielen Dank für die sehr schnelle Antwort. Es funktioniert.
    Ich muss offensichtlich noch viel lernen! Dein Artikel wird mir dabei Helfen.

    Grüße
    noenglish

  14. bressanl

    Tolle Erklärung. Hat mir sehr geholfen!

    Jedoch schaffe ich es nicht etwas praktisch gleiches selbst zu programmieren. Java gibt mir zwar keine Fehlermeldung doch wenn ich den sortierten Array ausgeben will, zeigt es mir in der Konsole nur [LBriefe;@60a896b8 an (Briefe ist meine Klasse).

    Hat sonst jemand diesen Fehler schon mal gesehen und weiss wie man ihn beheben kann?

  15. Stefan Kiesel

    Hallo bressanl,

    das ist kein Fehler in Java sondern ganz normal 😉 . Die Klasse Array hat keine toString-Methode und deshalb keine sinnvolle Ausgabe. Wenn Sie die Elemente eines Arrays ausgeben wollen, müssen Sie das entweder manuell machen, bspw.

    for (Briefe b : briefeArray) {
      System.out.println(b);
    }

    oder über die Arrays-Klasse:

    Arrays.toString(briefeArray);

    Beachten Sie, dass dann zumindest die Klasse Briefe die toString-Methode richtig überschrieben haben muss. Näheres finden Sie hier: 04.03.11 Besondere Methoden (equals, hashCode und toString). Alternativ können Sie auch die zuerst vorgestellte Variante wählen und lediglich die jeweils interessanten Attribute ausgeben.

    Ein allgemeiner Hinweis: Sie sollten Ihre Klasse nicht Briefe sondern nur Brief nennen. Die wenigsten Klassen rechtfertigen eine Benamung in der Mehrzahl. So stellt eine Instanz Ihrer Briefe-Klasse sicherlich nur einen Brief und nicht mehrere Briefe dar. Sollte Ihre Klasse tatsächlich mehrere Briefe und nicht nur einen Brief darstellen, sollten Sie hinterfragen, ob Ihr Anwendungsdesign korrekt ist.

    Grüße
    Stefan

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.