2016-03-10 10 views
8

Hier ist eine harte Nuss zu knacken. Ich habe einen Konflikt zwischen der Verwendung von Vararg und Generika zusammen. Nach bestimmtem Code:Kombination von Varargs und Generika für verkettete Vergleiche in Java

public class MyObject implements Comparable<MyObject> 
{ 
    private String name; 
    private int index; 

    @Override 
    public int compareTo(MyObject o) 
    { 
     if (name.compareTo(o.name) != 0) 
      return name.compareTo(o.name); 
     return ((Integer) index).compareTo(o.index); 
    } 
} 

Ich mag die compareTo Methode mehr verwenden, als eine Bedingung zu vergleichen. Wenn die Strings identisch sind, verwenden Sie stattdessen die Ints. Übliche Situation würde ich sagen.
Ich würde gerne eine statische Methode erstellen, um dies im Allgemeinen zu behandeln. Und ich möchte, dass die neue Methode chainedCompare wie folgt aufgerufen werden:

public int compareTo(MyObject o) 
{ 
    return chainedCompare(this, o, myO -> myO.name, myO -> myO.index); 
} 

Die Lambda-Ausdrücke sind varargs der Java-8-Schnittstelle Funktion. Also zuerst schrieb ich die Methode wie folgt aus:

public static <T, C extends Comparable<C>> int chainedCompare(T object1, T object2, Function<T, C>... comparisons) 
{ 
    int compareValue = 0; 
    for (Function<T, C> comparison : comparisons) 
    { 
     compareValue = comparison.apply(object1).compareTo(comparison.apply(object2)); 
     if (compareValue != 0) 
      break; 
    } 
    return compareValue; 
} 

Aber ich nicht der Meinung, dass in diesem Fall der generische Typ C die gleiche Art für alle Function<T, C> Vergleiche im varargs Array sein muss. Wie Sie oben sehen können, möchte ich verschiedene Vergleiche verwenden (wie im Beispiel String und Integer).
Dann modifiziert ich es auf diese Version:

public static <T> int chainedCompare(T object1, T object2, Function<T, ? extends Comparable<?>>... comparisons) 
{ 
    int compareValue = 0; 
    for (Function<T, ? extends Comparable<?>> comparison : comparisons) 
    { 
     compareValue = comparison.apply(object1).compareTo(comparison.apply(object2)); 
     if (compareValue != 0) 
      break; 
    } 
    return compareValue; 
} 

Typ C ist hier mit Platzhalter ersetzt. Während der Methodenaufruf jetzt funktioniert, kompiliert die Methode selbst nicht, da der Platzhalter typisierte Parameter compareTo ist.

Also auf der einen Seite brauche ich einen festen generischen Typ (erweitert Comparable) für die Funktionsschnittstelle, aber auf der anderen Seite brauche ich Funktionsschnittstellen von verschiedenen (zweiten) generischen Typen, wo Sie normalerweise einen Platzhalter setzen könnten. Wie löst man das?
Meine einzige Voraussetzung ist, dass ich die statische Methode so einfach aufrufen kann, wie es bei einer undefinierten Anzahl von Vergleichsbedingungen angezeigt wird.


Basierend auf den Vorschlägen von Tunaki ich war in der Lage, das Verfahren wie folgt zu ändern, die wie gewünscht verwendet werden können:

@SuppressWarnings("raw-types") 
public static <T> int chainedCompare(T object1, T object2, Function<T, ? extends Comparable>... comparisons) 
{ 
    return Arrays.stream(comparisons) 
     .map(Comparator::comparing) 
     .reduce(Comparator::thenComparing) 
     .map(c -> c.compare(object1, object2)) 
     .orElse(0); 
} 

public int compareTo(MyObject o) 
{ 
    return chainedCompare(this, o, myO -> myO.name, myO -> myO.index); 
} 

Antwort

4

Statt eine Comparable zu verwenden, wäre es einfacher, eine Comparator zu verwenden :

public static <T> int chainedCompare(T object1, T object2, Comparator<T>... comparators) { 
    int compareValue = 0; 
    for (Comparator<? super T> comparator : comparators) { 
     compareValue = comparator.compare(object1, object2); 
     if (compareValue != 0) 
      break; 
    } 
    return compareValue; 
} 

Sie auch alle Komparator zusammen thenComparing und haben mit Kette könnte

@SafeVarargs 
public static <T> int chainedCompare(T object1, T object2, Comparator<T>... comparators) { 
    return Arrays.stream(comparators) 
       .reduce(Comparator::thenComparing) 
       .map(c -> c.compare(object1, object2)) 
       .orElse(0); 
} 

Dann können Sie verwenden, um comparingIntComparator Objekte mit comparing(keyExtractor) oder der primitiven Spezialisierung zu konstruieren.

@Override 
public int compareTo(MyObject o) { 
    return chainedCompare(this, o, 
      Comparator.comparing(obj -> obj.name), 
      Comparator.comparingInt(obj -> obj.index) 
      ); 
} 

Mit diesem Ansatz kann man sogar die Existenz solcher Nutzen in Frage stellen und einfach

@Override 
public int compareTo(MyObject o) { 
    return Comparator.<MyObject, String> comparing(obj -> obj.name) 
        .thenComparingInt(obj -> obj.index) 
        .compare(this, o); 
} 
+0

haben, die einen schönen Ansatz. Es wäre noch schöner, wenn ich Comparator.comparing() nicht für jede Bedingung separat schreiben müsste. Da comparison() auch eine Funktion benötigt, wäre es cool, die Funktion varargs zu behalten und die Comparators innerhalb der chainedCompare-Methode zu konstruieren. Aber ich denke dann hätte ich das gleiche generische Problem wie vorher, oder? Ist das unlösbar? – Arceus

+0

@Arceus Ja, Sie würden unter dem gleichen Problem fallen. Beachten Sie, dass ich eine Bearbeitung mit einer anderen einfachen Lösung vorgenommen habe. – Tunaki

+0

In der Tat sind Ihre Vorschläge mögliche Lösungen. Danke an dieser Stelle. Aber meine wirkliche Sache war nicht nur, den Inhalt von Vergleichen zu einem Oneliner zu machen, sondern es so kurz wie möglich zu halten. Und ehrlich gesagt finde ich deinen Code ein wenig ausschweifend, da ich es in jedem überschriebenen compareTo so schreiben muss (das werden ziemlich viele sein). – Arceus