Как проверить ключ на карте независимо от случая?

Я хочу знать, присутствует ли определенный ключ в HashMap, поэтому я использую метод containsKey(key). Но он чувствителен к регистру, то есть он не возвращает истину, если есть ключ с именем, и я ищу имя. Так есть ли способ узнать, не беспокоясь о случае ключа?

Спасибо

9 ответов

Решение

Не с обычными картами.

"abc" является отличной строкой от "ABC", их хеш-коды отличаются, а их методы equals() возвращают false по отношению друг к другу.

Самое простое решение - просто вставить все входные данные в верхний или нижний регистр перед вставкой / проверкой. Вы могли бы даже написать свой собственный Map обертка, которая будет делать это для обеспечения согласованности.

Если вы хотите сохранить регистр ключа в том виде, в котором он указан, но с учетом сравнения без учета регистра, вы можете использовать TreeMap и предоставить собственный компаратор, который будет сравнивать без учета регистра. Тем не менее, подумайте, прежде чем идти по этому пути, так как вы получите некоторые непримиримые несоответствия - если кто-то звонит map.put("abc", 1) затем map.put("ABC", 2)в каком случае ключ хранится на карте? Можете ли вы даже понять это? Вас устраивает тот факт, что если кто-то обернет вашу карту стандартным HashMap вы потеряете функциональность? Или что, если кто-то и так перебирает ваш набор ключей, и делает свою быструю "проверку", используя equals() вы получите противоречивые результаты? Будет много других подобных случаев. Обратите внимание, что вы нарушаете контракт Map, выполняя это (поскольку равенство ключей определяется в терминах метода equals() для ключей), поэтому оно действительно не работает в любом смысле.

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

Использовать TreeMap который построен с String#CASE_INSENSITIVE_ORDER,

Map<String, String> map = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
map.put("FOO", "FOO");

System.out.println(map.get("foo")); // FOO
System.out.println(map.get("Foo")); // FOO
System.out.println(map.get("FOO")); // FOO

Вы можете использовать TreeMap с пользовательским, без учета регистра Comparator (который использует String.compareToIgnoreCase())

Например:

Map<String, Something> map = 
    new TreeMap<String, Something>(CaseInsensitiveComparator.INSTANCE);

class CaseInsensitiveComparator implements Comparator<String> {
    public static final CaseInsensitiveComparator INSTANCE = 
           new CaseInsensitiveComparator();

    public int compare(String first, String second) {
         // some null checks
         return first.compareToIgnoreCase(second);
    }
}

Обновление: кажется, что String уже определил это Comparator как константа.

Чтобы сохранить Map инварианты, вы можете просто сделать свои собственные ключи. Реализуйте разумно hashCode/equals и тебе хорошо идти

final class CaseInsensitive {
    private final String s;
    private final Local lc;
    public CaseInsensitive (String s, Locale lc) { 
        if (lc == null) throw new NullPointerException();
        this.s = s; 
        this.lc = lc; 
    }

    private s(){ return s == null ? null : s.toUpperCase(lc); }

    @Override
    public int hashCode(){ 
        String u = s();
        return (u == null) ? 0 : u.hashCode(); 
    }

    @Override
    public boolean equals(Object o){ 
        if (!getClass().isInstance(o)) return false;
        String ts = s(), os = ((CaseInsensitive)other).s();
        if (ts == null) return os == null;
        return ts.equals(os);
    }
}

// Usage:
Map<CaseInsensitive, Integer> map = ...;
map.put(new CaseInsensitive("hax", Locale.ROOT), 1337);
assert map.get(new CaseInsensitive("HAX", Locale.ROOT) == 1337;

Примечание: не все во всем мире согласны с тем, что является заглавной буквой того, что - известный пример, что заглавной версией "i" на турецком языке является "İ", а не "I".

Map использования equals а также hashCode проверить на равенство ключей, и вы не можете перезаписать их для String, Что вы можете сделать, это определить свой собственный класс Key, который содержит строковое значение, но реализует equals а также hashCode в нечувствительном к регистру способе.

Самый простой способ - сложить ключи самостоятельно, вставляя их и ища. Т.е.

map.put(key.toLowerCase(), value);

а также

map.get(key.toLowerCase());

Вы можете создать подкласс, например, HashMap, чтобы получить свой собственный класс с ними, если вы хотите, чтобы это делалось автоматически.

Создайте свою собственную обертку строкового класса, реализуйте equals и hashcode, используйте это как ключ в hashmap:

   class MyStringKey
   {
      private String string;
      public String getString()
      {
         return string;
      }
      public void setString(String string)
      {
         this.string = string;
      }

      public boolean equals(Object o)
      {
         return o instanceof MyStringKey && this.equalsIgnoreCase(((MyStringKey)o).getString());
      }

      public boolean hashCode()
      {
         return string.toLowerCase().hashcode(); //STRING and string may not have same hashcode
      }
   }

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

Этот ответ может быть утомительным, если вы добавите в свою карту во многих местах. В моем примере это происходит только тогда, когда пользователь создает нового персонажа (в моей игре). Вот как я справился с этим:

boolean caseInsensitiveMatch = false;
for (Map.Entry<String, Character> entry : MyServer.allCharacterMap.entrySet()) {
    if (entry.getKey().toLowerCase().equals(charNameToCreate.toLowerCase())){
        caseInsensitiveMatch = true;
        break;
    }
}

Конечно, это требует прохождения через мой большой ConcurrentHashMap, но работает для меня.

Другие вопросы по тегам