27

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

GuruKulki
  • 25,776
  • 50
  • 140
  • 201

9 Answers9

36

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.

Andrzej Doyle
  • 102,507
  • 33
  • 189
  • 228
  • Agree the best way is to just insert using upper or lower case. – bwawok Jun 22 '10 at 12:10
  • 3
    Unfortunately converting to upper/lower case fails "the Turkey test" (Google it and see what happens to the letter 'i'). If internationalisation is important, best to use `TreeMap` and yes - be aware of contract violation issues. (IIRC, calling `remove/retainAll` with certain collection classes may give unexpected results.) Better yet, use Guava's `ImmutableSortedMap/Set` where possible, which avoids inconsistent behaviour when given a custom comparator. – Luke Usherwood Dec 27 '15 at 21:43
  • https://stackoverflow.com/questions/796986/what-is-the-turkey-test – RamValli Mar 30 '18 at 03:40
20

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
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • 1
    There is a bug lurking here, uncommon but broken nevertheless: the call to `map.keyset().removeAll(c)` may or may not use the map's comparator - depending on the size of c! See [Sun bug 6394757](http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6394757). (I still use it.) Aside: Guava's `ImmutableSorted(Map|Set)` collections put your mind at rest, by strictly only using the Comparator, never equals. – Luke Usherwood Oct 04 '13 at 22:07
  • Use `TreeMap` if you don't care about complexity, performance is O(log n) compared to O(1) for `HashMap` – Silvia H Apr 02 '18 at 22:21
15

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.

Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140
  • 1
    `TreeMap` is a little dodgy in this case as it's easy to violate the contract of `Map`. `containsKey()` should "return true if and only if this map contains a mapping for a key `k` such that `(key==null ? k==null : key.equals(k))`" but of course it won't in this case. This *will* lead to inconsistencies at some point if the map is used in all but the most trivial way. – Andrzej Doyle Jun 22 '10 at 11:54
  • @Andrzej true but no worse than some other violations of the `Map` contract, e.g. `IdentityHashMap`, `TreeMap` – finnw Jun 22 '10 at 13:50
  • @Andrzej: It's fine as long as you acknowledge that it won't have semantics as defined by `Map`. Which is already stated in the `SortedMap` javadoc: *Note that the ordering maintained by a sorted map (whether or not an explicit comparator is provided) must be consistent with equals if the sorted map is to correctly implement the Map interface.*. It still works if they're not consistent, it just doesn't obey `Map` anymore. – Mark Peters Jun 22 '10 at 14:46
  • 6
    It's funny that you mention that String defines this `Comparator`, but don't tell us what it is: `String.CASE_INSENSITIVE_ORDER` – Powerlord Jun 22 '10 at 15:23
8

There's a CaseInsensitiveMap class in Apache commons

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

tim_yates
  • 167,322
  • 27
  • 342
  • 338
  • 1
    the bad thing about commons-collections is that they don't support generics, but it's good not to reinvent the wheel. – Bozho Jun 22 '10 at 11:49
7

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".

Paul
  • 19,704
  • 14
  • 78
  • 96
gustafc
  • 28,465
  • 7
  • 73
  • 99
6

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.

Péter Török
  • 114,404
  • 31
  • 268
  • 329
3

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.

Thorbjørn Ravn Andersen
  • 73,784
  • 33
  • 194
  • 347
0

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.

KisnardOnline
  • 653
  • 4
  • 16
  • 42
0

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
      }
   }
Nivas
  • 18,126
  • 4
  • 62
  • 76