Java Map, фильтр со свойствами значений

у меня есть

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

Я хотел бы фильтровать и сохранять только записи, значения которых содержат известную пару, скажем ('mike' = > 'jordan'), и избегать цикла, как показано ниже

есть ли в моих включенных библиотеках apache.commons и google.общий метод фильтра (который, вероятно, тоже будет делать цикл, но, по крайней мере, он менее подробный

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

5 ответов


вы можете использовать фильтры из гуавы и Predicate интерфейс.

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

Итак, простой пример:

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

вместо того, чтобы заставить ваш клиентский код использовать фильтр / цикл, создайте то, что вам нужно в API вашего класса:

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

кстати, если вы используете свой цикл, подумайте об изменении на это:

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

изменения в цикле:

  • изменен порядок равных, чтобы избежать NPE
  • используя iterator разрешить удаление записей непосредственно

даже если у вас нет класса, вы можете легко обернуть его в статическом метод в классе утилиты, где он также может быть легко параметризован для работы с любой вложенной картой:

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

вот не-guava impl для статического метода:

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


вот два примера. Оба печатают ключ на основе соответствия в свойствах значения.

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

один использует цикл и один использует Guava.

в то время как первый работает лучше, вы должны действительно решить, что будет проще всего понять и поддерживать.

некоторые предложения от @missingfaktor. Вы должны использовать свое собственное суждение, но он хорошо осветил некоторые из вопросов.

  1. много дублирования кода.
  2. специальная обработка корпуса.
  3. более цикломатическая сложность.
  4. больше шансов на ошибку, в результате первых трех пуль.
  5. трудно следовать кодексу.

представьте, что вы новый разработчик, который должен поддерживать эту программу. С чем бы вы предпочли столкнуться?


вы можете фильтровать карту с помощью java 8 и потоков. Первым шагом в этом процессе является преобразование в поток с помощью entrySet().stream(). Это дает вам Stream<Map.Entry<String, TreeMap<String, String>>. Затем вы можете использовать filter(...) для фильтрации списка. При фильтрации следует возвращать значение true, когда входящее значение должно быть включено в результат фильтра. После фильтрации результатов можно использовать foreach для выполнения цикла над конечным результатом.

конечный результат будет выглядеть следующим образом:

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