Aneinanderreihung von Comparatoren

Oftmals ist das Ordnungskriterium aus mehreren Bedingungen zusammengesetzt, wie die Sortierung in einem Telefonbuch zeigt. Erst gibt es eine Sortierung nach dem Nachnamen, dann folgt der Vorname. Um diese mit einem Compartor-Objekt zu lösen, müssen entweder alle Einzelvergleiche in ein neues Compartor-Objekt verpackt werden, oder einzelne Comparatoren zu einem „Super“-Comparator zusammengebunden werden – die zweite Lösung ist natürlich schöner, denn das erhöht die Wiederverwendbarkeit, denn einzelne Comparatoren können dann leicht für andere Zusammenhänge genutzt werden.

Comparatoren in eine Vergleichskette setzen

Am Anfang steht ein besonderer Comparator, der sich aus mehreren Comparatoren zusammensetzt. Immer dann, wenn ein Teil-Compartor bei zwei Objekten aussagt, dass sie gleich sind (der Vergleich liefert 0 ist), so soll der nächste Comparator die Endscheidung fällen – kann er das auch nicht, weil das Ergebnis wieder 0 ist, geht es zum nächsten Vergleicher.

Den Programmcode wollen wir einen neue Hilfsklasse ComparatorChain setzen:

package com.tutego.insel.util;

import java.util.*;

/**
 * A {@link Comparator} that puts one or more {@code Comparator}s in a sequence.
 * If a {@code Comparator} returns zero the next {@code Comparator} is taken.
 */
public class ComparatorChain<E> implements Comparator<E>
{
  private List<Comparator<E>> comparatorChain = new ArrayList<Comparator<E>>();
  
  /**
   * Construct a new comparator chain from the given {@code Comparator}s.
   * The argument is not allowed to be {@code null}.
   * @param comparators Sequence of {@code Comparator}s
   */
  @SafeVarargs  // ab Java 7
  public ComparatorChain( Comparator<E>... comparators )
  {
    if ( comparators == null )
      throw new IllegalArgumentException( "Argument is not allowed to be null" );

    Collections.addAll( comparatorChain, comparators );
  }

  /**
   * Adds a {@link Comparator} to the end of the chain.
   * The argument is not allowed to be {@code null}.
   * @param comparator {@code Comparator} to add
   */
  public void addComparator( Comparator<E> comparator )
  {
    if ( comparator == null )
      throw new IllegalArgumentException( "Argument is not allowed to be null" );

    comparatorChain.add( comparator );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int compare( E o1, E o2 )
  {
    if ( comparatorChain.isEmpty() )
      throw new UnsupportedOperationException(
                  "Unable to compare without a Comparator in the chain" );

    for ( Comparator<E> comparator : comparatorChain )
    {
      int order = comparator.compare( o1, o2 );
      if ( order != 0 )
        return order;
    }

    return 0;
  }
}

Die ComparatorChain können wir auf zwei Weisen mit den Comparator-Gliedern füttern: einmal zu Initialisierungszeit im Konstruktor, und dann später noch über die addComparator()-Methode. Beim Weg über den Konstruktor ist ab Java 7 die Annotation @SafeVarargs zu nutzen, da sonst die Kombination eines Varargs und Generics auf der Nutzerseite zu einer Warnung führt.

Ist kein Comparator intern in der Liste, wird das compare() eine Ausnahme auslösen. Der erste Comparator in der Liste ist auch das Vergleichsobjekt was zuerst gefragt wird. Liefert er ein Ergebnis ungleich 0 liefert das die Rückgabe der compare()-Methode. Ein Ergebnis gleich 0 führt zur Anfrage des nächstes Comparators in der Liste.

Wir wollen diese ComparatorChain für ein Beispiel nutzen, dass eine Liste nach Nach- und Vornamen sortiert.

package com.tutego.insel.util;

import java.util.*;

public class ComparatorChainDemo
{
  public static class Person
  {
    public String firstname, lastname;

    public Person( String firstname, String lastname )
    {
      this.firstname = firstname;
      this.lastname  = lastname;
    }

    @Override public String toString()
    {
      return firstname + " " + lastname;
    }
  }

  public final static Comparator<Person>
    PERSON_FIRSTNAME_COMPARATOR = new Comparator<Person>() {
      @Override public int compare( Person p1, Person p2 ) {
        return p1.firstname.compareTo( p2.firstname );
      }
    };

  public final static Comparator<Person>
    PERSON_LASTNAME_COMPARATOR = new Comparator<Person>() {
      @Override public int compare( Person p1, Person p2 ) {
        return p1.lastname.compareTo( p2.lastname );
      }
    };

  public static void main( String[] args )
  {
    List<Person> persons = Arrays.asList(
      new Person( "Onkel", "Ogar" ), new Person( "Olga", "Ogar" ),
      new Person( "Peter", "Lustig" ), new Person( "Lara", "Lustig" ) );

    Collections.sort( persons, PERSON_LASTNAME_COMPARATOR );
    System.out.println( persons );

    Collections.sort( persons, PERSON_FIRSTNAME_COMPARATOR );
    System.out.println( persons );

    Collections.sort( persons, new ComparatorChain<Person>(
        PERSON_LASTNAME_COMPARATOR, PERSON_FIRSTNAME_COMPARATOR ) );
    System.out.println( persons );
  }
}

Die Ausgabe ist:

[Peter Lustig, Lara Lustig, Onkel Ogar, Olga Ogar]

[Lara Lustig, Olga Ogar, Onkel Ogar, Peter Lustig]

[Lara Lustig, Peter Lustig, Olga Ogar, Onkel Ogar]

Ähnliche Beiträge

Veröffentlicht in Insel

8 Gedanken zu “Aneinanderreihung von Comparatoren

  1. Uij… Sehr gut. Nach so etwas hatte ich schon gesucht um mal kurz einen MultiItemSortierung in einem DataGrid zu realisieren. Danke.

  2. Alternative wäre die Nutzung des Dekorator-Pattern und Aufruf in etwa wie folgt: Collections.sort(persons, new ComparatorChain(PERSON_LASTNAME_COMPARATOR, new ComparatorChain(PERSON_FIRSTNAME_COMPARATOR)));

  3. Schöner wäre es, wenn die ComparatorChain als innere Comparatoren nicht Comparator erwarten würde, sondern das allgemeinere Comparator. Funktioniert genauso, und man ist bei der Wahl der Einzelkomparatoren nicht so eingeschränkt.

  4. Warum nicht einfach:

    public final static Comparator
    PERSON_FIRSTNAME_COMPARATOR = new Comparator() {
    @Override public int compare( Person p1, Person p2 ) {
    int order = p1.firstname.compareTo( p2.firstname );
    if (order == 0) order = p1.lastname.compareTo( p2.lastname );
    return order;
    }
    };

    Das Konstrukt macht doch nur mehr Komplexität ohne wirklichen Mehrwert.

  5. Wie schon in der Einleitung beschrieben:

    Oftmals ist das Ordnungskriterium aus mehreren Bedingungen zusammengesetzt, wie die Sortierung in einem Telefonbuch zeigt. Erst gibt es eine Sortierung nach dem Nachnamen, dann folgt der Vorname. Um diese mit einem Compartor-Objekt zu lösen, müssen entweder alle Einzelvergleiche in ein neues Compartor-Objekt verpackt werden, oder einzelne Comparatoren zu einem „Super“-Comparator zusammengebunden werden – die zweite Lösung ist natürlich schöner, denn das erhöht die Wiederverwendbarkeit, denn einzelne Comparatoren können dann leicht für andere Zusammenhänge genutzt werden.

    Oft gibt es Comparatoren schon, und so kann man sie verbinden, OHNE neu einen Komparator mit der gleichen Logik programmieren zu müssen. Diese kleinen Mini-Simple-Comparatoren sind ja nur ein Beispiel, jeder Comparator kann ja sehr komplex sein.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert