2012-06-22 14 views
6

ho unJava Map, filtro con valori proprietà

TreeMap resMap new TreeMap<String, Map<String, String>>(); 

Vorrei filtrare e mantenere solo le voci che valorizza contiene una coppia nota, diciamo ('Mike' => 'Jordan'), e evitare un ciclo come qui di seguito

c'è nei miei biblioteche apache.commons inclusi e google.common un metodo del filtro (che probabilmente farebbe un ciclo troppo, ma almeno è meno prolissa

for (Entry<String, TreeMap<String, String>> el : resMap.entrySet()){ 
    if (el.getValue().get("mike").equals("jordan")){ 
     // 
    } 
} 
+0

è necessario un qualche tipo di LINQ equivalente di DotNet in Java ... – Wins

+2

Perché si vuole evitare un ciclo? Qualunque cosa tu usi è come usare un loop per te. –

+3

Commento secondario: cambia l'ordine negli uguali, poiché get ("mike") potrebbe restituire null. cioè 'if (" jordan ".equals (el.getValue(). get (" mike "))' – user949300

risposta

9

È possibile utilizzare i filtri di Guava e l'interfaccia Predicate.

Predicate<T> yourFilter = new Predicate<T>() { 
    public boolean apply(T o) { 
     // your filter 
    } 
}; 

Così, semplice esempio potrebbe essere:

Predicate<Integer> evenFilter = new Predicate<Integer>() { 
    public boolean apply(Integer i) { 
     return (i % 2 == 0); 
    } 
}; 

Map<Integer, Integer> map = new HashMap<Integer, Integer>(); 

Map<Integer, Integer> evenMap = Maps.filterValues(map, evenFilter); 
+0

Penso, anche se il filtro guava impiega la stessa quantità di tempo per visualizzare una mappa più grande o più piccola, ma l'utilizzo di iterazione per mappe di dimensioni ridotte offre prestazioni migliori. –

0

Date un'occhiata allo Guava 's Predicates e Functions.

+0

Anche questi cicli di utilizzo. –

+3

Naturalmente! Rimuovono anche la duplicazione di tali loop dal codice dell'utente. –

+1

E sostituirli con predicati che possono essere più lunghi dei loop che sostituiscono. ;) –

4

Invece di forzare il codice client per utilizzare un filtro/loop, costruire quello che ti serve nella API della classe:

public class MyClass { 

    private TreeMap resMap new TreeMap<String, Map<String, String>>(); 

    public void filter(String key, String value) { 
     // Some impl here. Either your loop or the guava approach 
    } 
} 

BTW, se si utilizza il ciclo, pensare di cambiare a questo:

for (Iterator<Map.Entry<String, TreeMap<String, String>>> i = resMap.entrySet().iterator(); i.hasNext();) { 
    Map.Entry<String, TreeMap<String, String>> entry = i.next(); 
    if (value.equals(entry.getValue().get(key))) { 
     i.remove(); 
    } 
} 

Le modifiche al circuito sono:

  • ordine modificato di uguali per evitare NPE
  • Utilizzando iterator per consentire la rimozione di voci direttamente

Anche se non si dispone di un classe, potresti facilmente racchiuderlo in un metodo statico su una classe di utilità, dove potrebbe anche essere facilmente parametrizzato per funzionare con qualsiasi mappa nidificata:

public static <K1, K2, V> void filter(Map<K1, Map<K2, V>> map, K2 key, V value) { 
    // Some impl here 
} 

Ecco un impl non guava per il metodo statico:

for (Iterator<Map.Entry<K1, Map<K2, V>>> i = map.entrySet().iterator(); i.hasNext();) { 
    Map.Entry<K1, Map<K2, V>> entry = i.next(); 
    if (value.equals(entry.getValue().get(key))) { 
     i.remove(); 
    } 
} 
+0

Piuttosto che reinventare la ruota usa i quadri esistenti. –

+1

@EugeneKuleshov Bene, ma questa è una scelta di implementazione. L'obiettivo principale di questa risposta è fornire un'API in modo che il codice client non debba conoscere guava/loops ecc. Ho modificato la risposta per dire questo. – Bohemian

+0

@EugeneKuleshov Piuttosto che reinventare il ciclo, basta usare un ciclo che è un costrutto linguistico. ;) –

0

Ecco due esempi. Entrambi stampano la chiave in base alla corrispondenza nelle proprietà del valore.

private static void printMatchingEntriesUsingALoop(Map<String, Map<String, String>> resMap, String key, String value) { 
    for (Map.Entry<String, Map<String, String>> entry : resMap.entrySet()) 
     if (value.equals(entry.getValue().get(key))) 
      System.out.println(entry.getKey()); 
} 

private static void printMatchingEntriesUsingGuava(Map<String, Map<String, String>> resMap, final String key, final String value) { 
    Predicate<Map<String, String>> keyValueMatch = 
    new Predicate<Map<String, String>>() { 
     @Override 
     public boolean apply(@Nullable Map<String, String> stringStringMap) { 
      return value.equals(stringStringMap.get(key)); 
     } 
    }; 

    Maps.EntryTransformer<String, Map<String, String>, Void> printKeys = 
    new Maps.EntryTransformer<String, Map<String, String>, Void>() { 
     @Override 
     public Void transformEntry(@Nullable String s, 
       @Nullable Map<String, String> stringStringMap) { 
      System.out.println(s); 
      return null; 
     } 
    }; 

    Maps.transformEntries(Maps.filterValues(resMap, keyValueMatch), printKeys); 
} 

public static void main(String... args) { 
    Map<String, Map<String, String>> resMap = new TreeMap<String, Map<String, String>>(); 
    printMatchingEntriesUsingALoop(resMap, "first", "mike"); 
    printMatchingEntriesUsingGuava(resMap, "first", "mike"); 
} 

Uno utilizza un loop e uno usa Guava.

Mentre il primo si comporta meglio, è necessario decidere quale sarà il più facile da capire e mantenere.

Alcuni suggerimenti da @missingfaktor. Devi usare il tuo giudizio, ma ha evidenziato bene alcuni dei problemi.

  1. un sacco di duplicazione del codice.
  2. gestione di casi speciali.
  3. Più complessità ciclomatica.
  4. Più possibilità di errore, a seguito dei primi tre proiettili.
  5. Difficile da seguire codice.

Immagina di essere un nuovo sviluppatore che deve supportare questo software. Quale preferiresti essere di fronte?

0

È possibile filtrare la mappa utilizzando java 8 e flussi. Il primo passaggio in questo processo è la conversione in uno stream utilizzando entrySet().stream(). Questo ti dà un Stream<Map.Entry<String, TreeMap<String, String>>. È quindi possibile utilizzare filter(...) per filtrare l'elenco. Quando si filtra, si dovrebbe restituire true quando il valore in entrata deve essere incluso nel risultato del filtro. Dopo aver filtrato i risultati, è possibile utilizzare foreach per eseguire il loop del risultato finale.

Il risultato finale sarà simile al seguente:

resMap.entrySet().stream() 
     .filter(e -> el.getValue().get("mike").equals("jordan")) 
     .foreach(e -> { 
     // Do something with your entry here 
     });