1

I have a Map<String, Object> in which I need the String key to be case insensitive

Currently I am wrapping my String objects in a Wrapper class I've called CaseInsensitiveString the code for which looks like this:

    /**
    * A string wrapper that makes .equals a caseInsensitive match
    * <p>
    *     a collection that wraps a String mapping in CaseInsensitiveStrings will still accept a String but will now
    *     return a caseInsensitive match rather than a caseSensitive one
    * </p>
    */
public class CaseInsensitiveString {
    String str;

    private CaseInsensitiveString(String str) {
        this.str = str;
    }

    public static CaseInsensitiveString wrap(String str) {
        return new CaseInsensitiveString(str);
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null) return false;

        if(o.getClass() == getClass()) { //is another CaseInsensitiveString
            CaseInsensitiveString that = (CaseInsensitiveString) o;
            return (str != null) ? str.equalsIgnoreCase(that.str) : that.str == null;
        } else if (o.getClass() == String.class){ //is just a regular String
            String that = (String) o;
            return str.equalsIgnoreCase(that);
        } else {
        return false;
        }

    }

    @Override
    public int hashCode() {
        return (str != null) ? str.toUpperCase().hashCode() : 0;
    }

    @Override
    public String toString() {
        return str;
    }
}

I was hoping to be able to get a Map<CaseInsensitiveString, Object> to still accept a Map#get(String) and return the value without having to do Map#get(CaseInsensitiveString.wrap(String)). In my testing however, my HashMap has returned null whenever I have tried to do this but it does work if I wrap the String before calling get()

Is it possible to allow my HashMap to accept both String and CaseInsensitiveString parameters to the get method and work in a caseInsensitive fashion regardless of if the String is wrapped or not, and if so, what am I doing wrong?

for reference my test code looks like this:

    Map<CaseInsensitiveString, String> test = new HashMap<>();
    test.put(CaseInsensitiveString.wrap("TesT"), "value");
    System.out.println(test.get("test"));
    System.out.println(test.get(CaseInsensitiveString.wrap("test")));

and returns:

null
value
Jim Garrison
  • 85,615
  • 20
  • 155
  • 190
CrypticCabub
  • 109
  • 2
  • 9
  • 3
    Depending on what you want, mainly speed considerations, you can use a `TreeMap` with [`String.CASE_INSENSITIVE_ORDER`](https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#CASE_INSENSITIVE_ORDER). – Boris the Spider Aug 18 '16 at 19:35
  • 1
    Simply extending your own map and converting the String to upper/lower case when putting and getting doesn't do the trick? – Florian Schaetz Aug 18 '16 at 19:38
  • 2
    you can store all keys in say lower case (or upper).. and before queries, convert the query string to lower case. – Jos Aug 18 '16 at 19:38
  • There is [Apache Commons CaseInsensitveMap](https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/map/CaseInsensitiveMap.html) as well – bradimus Aug 18 '16 at 19:40
  • those solutions of converting to upper/lower case may be workable here, however I'm dealing with an interface to an external api that uses CasePreservation and I was hoping to maintain that if at all possible – CrypticCabub Aug 18 '16 at 19:43
  • my returned map in question is a mapping of usernames to IDs after a bulk query to a web-based api – CrypticCabub Aug 18 '16 at 19:44

2 Answers2

5

You can do it like this:

Map<String, Object> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);

See this question.

However, note the performance implications of using a TreeMap instead of a HashMap, as mentioned by Boris in the comments.

Community
  • 1
  • 1
mapeters
  • 1,067
  • 7
  • 11
  • If you think this is a duplicate, there are close reasons for that. Please flag (or vote once you have enough reputation) to close as such. – Sotirios Delimanolis Aug 18 '16 at 19:53
  • @SotiriosDelimanolis I didn't realize I had permissions to flag questions, thanks! – mapeters Aug 18 '16 at 19:54
  • That's perfect! and thanks for the link, sorry I didn't find it before :) – CrypticCabub Aug 18 '16 at 19:59
  • @CrypticCabub Glad to help! Yeah, maybe try to do a bit more googling next time before posting a question, but no worries this time! – mapeters Aug 18 '16 at 20:01
  • 2
    Make sure that the OP understands that this is a **tree** rather than a hash. So `put` is `O(lg n)` rather than `O(1)`. `get` is also `O(lg n)` rather than `O(1)`. So this will be significantly slower if speed is a concern. – Boris the Spider Aug 18 '16 at 20:27
0

This is as expected:

See this:

Map<CaseInsensitiveString, String> test = new HashMap<>();

This line tells MAP to accept only CaseInsensitiveString objects, when you pass another object to the map it treats as unknown key and returns null.

You can get your required behavior by changing this to :

Map<Object, String> test = new HashMap<>();
Amit Mahajan
  • 895
  • 6
  • 34