开发者

How to check for key in a Map irrespective of the case? [duplicate]

This question already has answers here: 开发者_StackOverflow Is there a good way to have a Map<String, ?> get and put ignoring case? [duplicate] (8 answers) Closed 7 years ago.

I want to know whether a particular key is present in a HashMap, so i am using containsKey(key) method. But it is case sensitive ie it does not returns true if there is a key with Name and i am searching for name. So is there any way i can know without bothering the case of the key?

thanks


Not with conventional maps.

"abc" is a distinct string from "ABC", their hashcodes are different and their equals() methods will return false with respect to each other.

The simplest solution is to simply convert all inputs to uppercase (or lowercase) before inserting/checking. You could even write your own Map wrapper that would do this to ensure consistency.

If you want to maintain the case of the key as provided, but with case-insensitive comparison, you could look into using a TreeMap and supplying your own Comparator that will compare case-insensitively. However, think hard before going down this route as you will end up with some irreconcilable inconsistencies - if someone calls map.put("abc", 1) then map.put("ABC", 2), what case is the key stored in the map? Can you even make this make sense? Are you comfortable with the fact that if someone wraps your map in a standard e.g. HashMap you'll lose functionality? Or that if someone happens to be iterating through your keyset anyway, and does their own quick "contains" check by using equals() you'll get inconsistent results? There will be lots of other cases like this too. Note that you're violating the contract of Map by doing this (as key equality is defined in terms of the equals() method on the keys) so it's really not workable in any sense.

Maintaining a strict uppercase map is much easier to work with and maintain, and has the advantage of actually being a legal Map implementation.


Use a TreeMap which is constructed with 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


You can use a TreeMap with a custom, case-insensitive Comparator (that uses String.compareToIgnoreCase())

For example:

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

Update: it seems that String has already defined this Comparator as a constant.


There's a CaseInsensitiveMap class in Apache commons

http://commons.apache.org/collections/


To preserve the Map invariants, you could just make your own keys. Implement sensible hashCode/equals and you're good to go:

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;

Note: Not everyone in the whole world agrees about what is uppercase of what - a famous example is that the upper-case version of "i" in Turkish is "İ", not "I".


Map uses equals and hashCode to test for key equality, and you can't overwrite these for String. What you could do is define your own Key class which contains a string value, but implements equals and hashCode in a case insensitive way.


The easiest way is to fold the keys yourself when inserting them and looking them up. I.e.

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

and

map.get(key.toLowerCase());

You could subclass e.g. HashMap to get your own class with these, if you want this automatically done.


create your own wrapper of string class, implement equals and hashcode, use this as the key in the 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
      }
   }


In an attempt to present an answer that matches your question's requirement "without bothering the case of the key"...

This answer may be tedious if you add into your map in many, many places. In my example it only happens when a user creates a new character (in my game). Here is how I handled this:

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

Of course this requires looping through my large ConcurrentHashMap, but works for me.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜