Java: универсальная функция карты фильтра - PullRequest
3 голосов
/ 21 февраля 2011

Я пытаюсь разработать универсальную функцию для фильтрации карт.

Код, который у меня пока есть:

public static Map<?, ?> filterAttrs(Map<?, ?> args, String... unless) {

    Map<?, ?> filteredAttrs = Map.class.newInstance();

    Arrays.sort(unless);
    for (Object o : args.keySet()) {
        if (Arrays.binarySearch(unless, o.toString()) < 0 ) {
            filteredAttrs.put(o, args.get(o));
        }
    }
    return filteredAttrs;
}

Я получаю следующие ошибки в FilterAttrs.put * 1006.*

Метод, помещенный (capture # 5-of?, Capture # 6-of?) В тип Map, не применим для аргументов (Object, capture # 8-of?)

Я не знаю, как создать универсальный Map (я пытался с 1Map.class.newInstance () `).

Есть идеи?

Редактировать: после прочтенияво многих ответах проблема состоит в том, как сделать filteredAttrs экземпляром того же типа, что и args.(Map) args.getClass().newInstance() кажется, добивается цели.

Ответы [ 5 ]

8 голосов
/ 21 февраля 2011

Проблема с этим кодом состоит в том, что система типов не позволяет вам помещать объект в Map с типом ключа ?.Это потому, что если тип ключа ?, компилятор не знает, что на самом деле хранится на карте - это может быть Object, или Integer, или List<Object> - и поэтому он не может подтвердить, чтото, что вы пытаетесь добавить на карту, на самом деле имеет правильный тип и не будет неуместным в Map.Например, если у вас есть этот метод:

public static void breakMyMap(Map<?, ?> m) {
    m.put(new Object(), new Object()); // Won't compile
}

, а затем напишите код, подобный следующему:

Map<String, String> myMap = new HashMap<String, String>();
breakMyMap(myMap);

Тогда, если код в breakMyMap будет скомпилирован, он поместитпара Object s в качестве ключей и значений в Map<String, String>, нарушая инвариант, что все элементы действительно String s.

Чтобы исправить это, вместо того, чтобы заставить эту функцию работать на Map<?, ?>, измените функцию, чтобы у вас было больше информации о типе ключей и значений.Например, вы можете попробовать это:

public static <K, V> Map<K, V> filterAttrs(Map<K, V> args, String... unless) {

    Map<K, V> filteredAttrs = new HashMap<K, V>();

    Arrays.sort(unless);
    for (K o : args.keySet()) {
        String attr = o.toString();
        if (Arrays.binarySearch(unless, o.toString()) < 0 ) {
            filteredAttrs.put(o, args.get(o));
        }
    }
    return filteredAttrs;
}

Теперь, когда компилятор знает, что тип ключа K, он может проверить, что put не будет смешивать типы ключей вmap.

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

Map<?, ?> filteredAttrs = Map.class.newInstance();

вызовет исключение во время выполнения, потому что Map является интерфейсом , а не классом, и поэтому пытается использовать newInstance для созданияэкземпляр этого не будет работать правильно.Чтобы это исправить, вы можете либо явно указать тип карты (как я делал в приведенном выше коде), либо получить класс аргумента:

Map<K, V> filteredAttrs = args.getClass().newInstance();

Конечно, это не такгарантированно будет работать, хотя общий контракт для коллекций состоит в том, что все коллекции должны иметь конструктор без аргументов.

Надеюсь, это поможет!

5 голосов
/ 21 февраля 2011

Я считаю плохим, если люди изобретают велосипед (и не делают ничего лучше).

Чтобы исправить все ваши проблемы в одном предложении: Можете ли вы попробовать Google Guava ?

Например, тогда вы можете использовать Карты :

  • Maps.filterEntries
  • Maps.filterKeys
  • Maps.filterValues ​​

Или, по крайней мере, написать фильтр, который принимает предикат в качестве аргумента.

3 голосов
/ 21 февраля 2011

Вы ничего не можете поместить в Map<?, ?>, потому что компилятор точно не знает, что это за карта. В строке:

filteredAttrs.put(o, args.get(o));

вы притворяетесь, что Map<?, ?> - это Map<Object, Object>, но это не то, что есть. Часто задаваемые вопросы по Java Generics от Angelika Langer объясняют , почему это не работает более подробно.

Вы должны добавить параметры типа к методу; тогда также нет необходимости создавать новую карту с помощью отражения:

public static <K, V> Map<K, V> filterAttrs(Map<K, V> args, String... unless) {
    Map<K, V> filteredAttrs = new HashMap<K, V>();

    Arrays.sort(unless);
    for (K o : args.keySet()) {
        if (Arrays.binarySearch(unless, o.toString()) < 0) {
            filteredAttrs.put(o, args.get(o));
        }
    }
    return filteredAttrs;
}
3 голосов
/ 21 февраля 2011

Эта строка

Map<?, ?> filteredAttrs = Map.class.newInstance();

должен выдавать InstantiationException - потому что Map - это интерфейс - если вы не реализовали свой собственный класс с именем Map

2 голосов
/ 21 февраля 2011

Вы не можете создать экземпляр Map, потому что это интерфейс. Не используйте ?, потому что это может вызвать путаницу.

public static <T, K> Map<T, K> filterAttrs(Map<T, K> args, T... unless) {
    Map<T, K> filteredAttrs;

    filteredAttrs = new HashMap<T, K>();
    Arrays.sort(unless);
    for (T o : args.keySet()) {
        if (Arrays.binarySearch(unless, o) < 0) {
            filteredAttrs.put(o, args.get(o));
        }
    }

    return filteredAttrs;
}

У вас есть эта строка, которая ничего не делает, поэтому я удалил ее: -

String attr = o.toString();
...