2016-03-10 13 views
8

Ecco un dado difficile da decifrare. Ho uno scontro tra l'uso di vararg e generici insieme. Codice seguente data:Combinazione di vararg e generici per i confronti concatenati 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); 
    } 
} 

voglio il metodo compareTo di usare più di un confronto condizione. Se le stringhe sono uguali, usa invece le int. Situazione normale direi.
Mi piacerebbe creare un metodo statico per gestirlo in generale. E voglio che il nuovo metodo chainedCompare a essere chiamato in questo modo:

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

I lambda sono varargs dell'interfaccia Funzione Java 8. Quindi, prima ho scritto il metodo del genere:

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; 
} 

Ma io non ritengo che in questo caso il tipo generico C deve essere dello stesso tipo per tutti i confronti Function<T, C> nella matrice varargs. Come puoi vedere sopra, voglio usare diversi Comparables (come String e Integer nell'esempio).
Poi ho modificato a questa versione:

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; 
} 

tipo C è qui sostituita con caratteri jolly. Mentre la chiamata al metodo funzionerebbe ora, il metodo stesso non viene compilato, a causa del parametro jolly digitato di compareTo.

Quindi, da un lato, ho bisogno di un tipo generico fisso (estensioni Comparabili) per l'interfaccia Function, ma d'altra parte ho bisogno di interfacce di funzione di diversi (secondi) tipi generici in cui di solito è possibile impostare un carattere jolly. Come risolvere questo?
Il mio unico requisito è che io possa chiamare il metodo statico semplice come mostrato con un numero indefinito di condizioni di confronto.


Sulla base dei suggerimenti di Tunaki sono stato in grado di modificare il metodo come segue che può essere utilizzato come desiderato:

@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); 
} 

risposta

4

Invece di utilizzare un Comparable, sarebbe più facile da usare un Comparator :

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; 
} 

Si potrebbe anche concatenare tutto il comparatore insieme usando thenComparing e hanno

@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); 
} 

quindi è possibile utilizzare che con la costruzione di oggetti con Comparatorcomparing(keyExtractor) o la specializzazione primitiva comparingInt.

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

Con questo approccio, si può anche mettere in discussione l'esistenza di tale utilità e semplicemente

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

Questo è un approccio piacevole. Sarebbe ancora più bello se non dovessi scrivere Comparator.comparing() per ogni condizione separatamente. Poiché comparing() accetta anche una funzione, sarebbe bello mantenere le varargs Function e costruire i Comparators all'interno del metodo chainedCompare. Ma immagino che avrei lo stesso problema generico di prima, giusto? È irrisolvibile? – Arceus

+0

@Arceus Sì, cadresti nello stesso problema. Si noti che ho apportato una modifica con un'altra soluzione semplice. – Tunaki

+0

In realtà i tuoi suggerimenti sono possibili soluzioni. Grazie a questo punto. Ma il mio vero problema non era solo quello di rendere il contenuto del confronto come un oneliner, ma per mantenerlo il più breve possibile. E onestamente trovo il tuo codice un po 'prolisso, dato che dovrò scriverlo così in ogni confronto sovrascritto (che sarà piuttosto numeroso). – Arceus