Как проверить ключ на карте независимо от случая?
Я хочу знать, присутствует ли определенный ключ в 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, но работает для меня.