Weiter zum Inhalt

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

{ 15 } Comments

  1. Olga | 1. Juni 2009 um 22:51 | Permalink

    Super!! Kann nicht besser sein!!!

  2. Markus | 21. Juli 2009 um 11:28 | Permalink

    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);
    		}
    	}
    }
  3. Ralph | 9. August 2009 um 16:30 | Permalink

    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.)

  4. Stefan Kiesel | 10. August 2009 um 07:07 | Permalink

    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

  5. Mur Votema | 15. Dezember 2010 um 16:51 | Permalink

    Vielen Dank für sehr gute Erklärung.
    Ich habe immer gedacht, das wäre komplizierter.

    Grüße,

    Mur

  6. Maddin | 14. Januar 2011 um 20:25 | Permalink

    Danke, hat mir gut geholfen bei einem Beleg.

  7. Marian | 4. Februar 2011 um 14:21 | Permalink

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

  8. ricky | 14. November 2011 um 12:52 | Permalink

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

  9. Stefan Kiesel | 14. November 2011 um 18:39 | Permalink

    Indem man in der API-Dokumentation nachliest

  10. Thomas B | 28. Dezember 2011 um 20:17 | Permalink
    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

  11. Stefan Kiesel | 28. Dezember 2011 um 20:42 | Permalink

    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

  12. Thomas B | 29. Dezember 2011 um 17:16 | Permalink

    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

  13. Stefan Kiesel | 29. Dezember 2011 um 17:45 | Permalink

    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

  14. hans peter | 21. Januar 2012 um 19:35 | Permalink

    Wie kann ich die Bierflaschen nach der Flaschengröße sortieren?

  15. Stefan Kiesel | 21. Januar 2012 um 20:39 | Permalink

    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

Kommentar verfassen

Dein E-Mail wird nicht veröffentlicht oder weitergegeben. Pflichtfelder sind mit * markiert.
*