2015-02-24 10 views
5

Quindi, cerchiamo di immaginare ho il seguente elenco:versione più avanzata del Collections.frequency()

List<Foo> myList = getListFromSomePlace(); 
int frequency = Collections.frequency(myList, someFoo); 

Questo conterà tutti someFoo elementi corrispondenti.

Tuttavia, se ho una versione più "complesso":

List<Foo> myList = getListFromSomePlace(); 
int frequency = /* get number of Elements in the List whose getInternalFoo() match a certain value */ 

Un modo per farlo sarebbe quello di sovrascrivere il metodo equals nella classe Foo, ma mi piacerebbe davvero evitare di mettere comportamento personalizzato nella classe Foo, specialmente perché potrei voler ottenere la frequenza in base a proprietà diverse dalla classe Foo, e posso avere solo una versione del metodo sovrascritto equals.

Funzioni come Collections.sort mi consentono di passare un comparatore personalizzato che farà esattamente ciò di cui ho bisogno, ma Collections.frequency non fornisce questo.

Con Java8, vorrei utilizzare un flusso e qualche espressione Lambda per risolvere questo problema, ma vorrei vedere se c'è una soluzione semplice che funzionerà con Java 7. Sto cercando qualcosa che non implichi codifica il metodo di frequenza personalizzato da solo, ma utilizzando alcune API esistenti. C'è qualcosa?

risposta

2

Non credo che lo standard JDK fornisca questo (Java < = 7). Se si desidera una soluzione che funzioni con Java 7 e che non implichi la codifica, è possibile utilizzare Guava e il relativo metodo Lists.transform.

Sarà assomiglia a questo:

List<Foo> myList = getListFromSomePlace(); 
int frequency = Collections.frequency(Lists.transform(myList, new Function<Foo, MyObject>() { 
    @Override 
    public MyObject apply(Foo input) { 
     return input.getInternalFoo(); 
    } 
}), myCriteria); 

Se ancora pensate che non vale la pena di aggiungere una libreria di terze parti per questo, si può ancora scrivere la propria interfaccia funzioni, e una classe di utilità che forniscono un metodo che trasforma un List<T> in un List<U> fornito il mapping da applicare. Non è molto difficile e non richiede molte righe di codice.

Scrivere la tua implementazione ti permetterebbe di farlo in un solo passaggio.

static <T, U> int frequency(Collection<T> coll, Function<? super T, ? extends U> mapper, U criteria) { 
    Objects.requireNonNull(coll); 
    Objects.requireNonNull(mapper); 
    Objects.requireNonNull(criteria); 
    int frequency = 0; 
    for(T t : coll) { 
     if(criteria.equals(mapper.apply(t))) { 
      frequency++; 
     } 
    } 
    return frequency; 
} 
+0

"Anche questa tecnica richiederà di ripetere due volte l'elenco. Una volta per applicare la mappatura; e una volta per controllare la frequenza. "Questo è falso, la trasformazione viene eseguita pigramente e verrà eseguita solo durante l'implementazione della frequenza. –

+0

@LouisWasserman Oh, sì, hai assolutamente ragione, grazie per il resto. –

+0

I Selezionando questa risposta come "Risposta corretta", le altre due risposte danno anche degli approcci interessanti. Seleziono questo perché la trasformazione di Guava consente un'implementazione che è la più simile a quella che sto cercando, specialmente perché lo fa non implica alcun 'hack',' duplicazione del codice', né più codice 'boilerplate'. Howerver, non significa che deve essere la soluzione più adatta per tutti o per ogni situazione ... – Martin

6

non credo che si può evitare di scrivere il proprio metodo. Rendilo privato se non vuoi inquinare la tua API.

public static <T> int frequency(Collection<T> c, T o, Comparator<T> comp) { 
    int freq = 0; 
    for(T e : c) { 
     if(o == null ? e == null : comp.compare(o, e) == 0) { 
      ++freq; 
     } 
    } 
    return freq; 
} 
+0

D'accordo, non è come 'frequency()' è un metodo complicato. – dimo414

2

Just 'decorare' il tuo someFoo sovrascrivendo equals() secondo le vostre esigenze:

List<Foo> myList = getListFromSomePlace(); 
final Foo someFoo = getSomeFooToGetItsFrequency(); 

int frequency = Collections.frequency(myList, new Foo() { 
    @Override 
    public boolean equals(Object another) { 
     if (another == someFoo) { 
      return true; 
     } 
     if ((another == null) || (someFoo == null)) { 
      return false; 
     } 
     if (another.getClass() != someFoo.getClass()) { 
      return false; 
     } 
     Foo anotherFoo = (Foo) another; 

     // Compare someFoo to anotherFoo as you wish here 

     return comparisonResult; 
    } 
}); 

Ora, questo funziona perché Collections.frequency() controlli di implementazione se l'argomento oggetto equals() ogni elemento della lista e non il contrario viceversa. Se quest'ultimo fosse vero, la frequenza restituita sarebbe sempre 0.

Come lei ha detto 'potrebbe desiderare di ottenere la frequenza in base a diversi oggetti di classe Foo', si potrebbe spostare la prima parte del metodo della classe interna anonima equals() ad una generica classe astratta:

public abstract class ComplexFrequency<T> { 

    private final T self; 

    public ComplexFrequency(T self) { 
     this.self = self; 
    } 

    @Override 
    public boolean equals(Object another) { 
     if (another == this.self) { 
      return true; 
     } 
     if ((another == null) || (this.self == null)) { 
      return false; 
     } 
     if (another.getClass() != this.self.getClass()) { 
      return false; 
     } 

     // Let subclasses compare both objects 
     return this.equals(this.self, (T) another); 
    } 

    protected abstract boolean equals(T self, T another); 
} 

Quindi, creare una sottoclasse di ComplexFrequency che mette a confronto come si desidera:

public class FooComparingPropertyA extends ComplexFrequency<Foo> { 

    public FooComparingPropertyA(Foo someFoo) { 
     super(someFoo); 
    } 

    @Override 
    protected boolean equals(Foo self, Foo another) { 
     // check equality based on propertyA 
    } 
} 

e, infine, 'decorare' il tuo someFoo utilizzando questa sottoclasse e superare l'istanza 'decorato' a Collections.frequency():

List<Foo> myList = getListFromSomePlace(); 
Foo someFoo = getSomeFooToGetItsFrequency(); 

int frequency = Collections.frequency(myList, new FooComparingPropertyA(someFoo));