Check ArrayList<String> contains("") method with equalsIgnoreCase [duplicate] - java

This question already has answers here:
ArrayList contains case sensitivity
(20 answers)
Closed 9 years ago.
I have an ArrayList();
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("BBB");
list.add("cCc");
System.out.println(list.contains("aAa"));
Here i want to check contains() method with equalsIgnoreCase method in same line.
How can i do it?

boolean containsEqualsIgnoreCase(Collection<String> c, String s) {
for (String str : c) {
if (s.equalsIgnoreCase(str)) {
return true;
}
}
return false;
}

You can't. The contract of contains is that it defers to equals. That's a fundamental part of the Collection interface. You have to write a custom method that iterates through the list and checks each value.

This is an interesting question from an OO perspective.
One possibility is to transfer the responsibility of the contract you want to enforce (equality without case) to the collected elements themselves, not to the list, with respect to a proper separation of concern.
You would then add a new class for your String objects (without inheritance, String class is final) where you would implement your own hashCode/equals contract.
// Strictly speaking, this is not a String without case, since only
// hashCode/equals methods discard it. For instance, we would have
// a toString() method which returns the underlying String with the
// proper case.
public final class StringWithoutCase {
private final String underlying;
public StringWithoutCase(String underlying) {
if (null == underlying)
throw new IllegalArgumentException("Must provide a non null String");
this.underlying = underlying;
}
// implement here either delegation of responsibility from StringWithoutCase
// to String, or something like "getString()" otherwise.
public int hashCode() {
return underlying.toLowerCase().hashCode();
}
public boolean equals(Object other) {
if (! (other instanceof StringWithoutCase))
return false;
return underlying.equalsIgnoreCase(other.underlying);
}
}
The objects populating the collection would be instances of StringWithoutCase :
Collection<StringWithoutCase> someCollection = ...
someCollection.add(new StringWithoutCase("aaa"));
someCollection.add(new StringWithoutCase("BBB"));
someCollection.add(new StringWithoutCase("cCc"));

Related

Checking for duplicated data in Java array list [duplicate]

I want to check whether a List contains an object that has a field with a certain value. Now, I could use a loop to go through and check, but I was curious if there was anything more code efficient.
Something like;
if(list.contains(new Object().setName("John"))){
//Do some stuff
}
I know the above code doesn't do anything, it's just to demonstrate roughly what I am trying to achieve.
Also, just to clarify, the reason I don't want to use a simple loop is because this code will currently go inside a loop that is inside a loop which is inside a loop. For readability I don't want to keep adding loops to these loops. So I wondered if there were any simple(ish) alternatives.
Streams
If you are using Java 8, perhaps you could try something like this:
public boolean containsName(final List<MyObject> list, final String name){
return list.stream().filter(o -> o.getName().equals(name)).findFirst().isPresent();
}
Or alternatively, you could try something like this:
public boolean containsName(final List<MyObject> list, final String name){
return list.stream().map(MyObject::getName).filter(name::equals).findFirst().isPresent();
}
This method will return true if the List<MyObject> contains a MyObject with the name name. If you want to perform an operation on each of the MyObjects that getName().equals(name), then you could try something like this:
public void perform(final List<MyObject> list, final String name){
list.stream().filter(o -> o.getName().equals(name)).forEach(
o -> {
//...
}
);
}
Where o represents a MyObject instance.
Alternatively, as the comments suggest (Thanks MK10), you could use the Stream#anyMatch method:
public boolean containsName(final List<MyObject> list, final String name){
return list.stream().anyMatch(o -> name.equals(o.getName()));
}
You have two choices.
1. The first choice, which is preferable, is to override the `equals()` method in your Object class.
Let's say, for example, you have this Object class:
public class MyObject {
private String name;
private String location;
//getters and setters
}
Now let's say you only care about the MyObject's name, that it should be unique so if two `MyObject`s have the same name they should be considered equal. In that case, you would want to override the `equals()` method (and also the `hashcode()` method) so that it compares the names to determine equality.
Once you've done this, you can check to see if a Collection contains a MyObject with the name "foo" by like so:
MyObject object = new MyObject();
object.setName("foo");
collection.contains(object);
However, this might not be an option for you if:
You are using both the name and location to check for equality, but you only want to check if a Collection has any `MyObject`s with a certain location. In this case, you've already overridden `equals()`.
`MyObject` is part of an API that you don't have liberty to change.
If either of these are the case, you'll want option 2:
2. Write your own utility method:
public static boolean containsLocation(Collection<MyObject> c, String location) {
for(MyObject o : c) {
if(o != null && o.getLocation.equals(location)) {
return true;
}
}
return false;
}
Alternatively, you could extend ArrayList (or some other collection) and then add your own method to it:
public boolean containsLocation(String location) {
for(MyObject o : this) {
if(o != null && o.getLocation.equals(location)) {
return true;
}
}
return false;
}
Unfortunately there's not a better way around it.
This is how to do it using Java 8+ :
boolean isJohnAlive = list.stream().anyMatch(o -> "John".equals(o.getName());
Google Guava
If you're using Guava, you can take a functional approach and do the following
FluentIterable.from(list).find(new Predicate<MyObject>() {
public boolean apply(MyObject input) {
return "John".equals(input.getName());
}
}).Any();
which looks a little verbose. However the predicate is an object and you can provide different variants for different searches. Note how the library itself separates the iteration of the collection and the function you wish to apply. You don't have to override equals() for a particular behaviour.
As noted below, the java.util.Stream framework built into Java 8 and later provides something similar.
Collection.contains() is implemented by calling equals() on each object until one returns true.
So one way to implement this is to override equals() but of course, you can only have one equals.
Frameworks like Guava therefore use predicates for this. With Iterables.find(list, predicate), you can search for arbitrary fields by putting the test into the predicate.
Other languages built on top of the VM have this built in. In Groovy, for example, you simply write:
def result = list.find{ it.name == 'John' }
Java 8 made all our lives easier, too:
List<Foo> result = list.stream()
.filter(it -> "John".equals(it.getName())
.collect(Collectors.toList());
If you care about things like this, I suggest the book "Beyond Java". It contains many examples for the numerous shortcomings of Java and how other languages do better.
Binary Search
You can use Collections.binarySearch to search an element in your list (assuming the list is sorted):
Collections.binarySearch(list, new YourObject("a1", "b",
"c"), new Comparator<YourObject>() {
#Override
public int compare(YourObject o1, YourObject o2) {
return o1.getName().compareTo(o2.getName());
}
});
which will return a negative number if the object is not present in the collection or else it will return the index of the object. With this you can search for objects with different searching strategies.
Map
You could create a Hashmap<String, Object> using one of the values as a key, and then seeing if yourHashMap.keySet().contains(yourValue) returns true.
Eclipse Collections
If you're using Eclipse Collections, you can use the anySatisfy() method. Either adapt your List in a ListAdapter or change your List into a ListIterable if possible.
ListIterable<MyObject> list = ...;
boolean result =
list.anySatisfy(myObject -> myObject.getName().equals("John"));
If you'll do operations like this frequently, it's better to extract a method which answers whether the type has the attribute.
public class MyObject
{
private final String name;
public MyObject(String name)
{
this.name = name;
}
public boolean named(String name)
{
return Objects.equals(this.name, name);
}
}
You can use the alternate form anySatisfyWith() together with a method reference.
boolean result = list.anySatisfyWith(MyObject::named, "John");
If you cannot change your List into a ListIterable, here's how you'd use ListAdapter.
boolean result =
ListAdapter.adapt(list).anySatisfyWith(MyObject::named, "John");
Note: I am a committer for Eclipse ollections.
Predicate
If you dont use Java 8, or library which gives you more functionality for dealing with collections, you could implement something which can be more reusable than your solution.
interface Predicate<T>{
boolean contains(T item);
}
static class CollectionUtil{
public static <T> T find(final Collection<T> collection,final Predicate<T> predicate){
for (T item : collection){
if (predicate.contains(item)){
return item;
}
}
return null;
}
// and many more methods to deal with collection
}
i'm using something like that, i have predicate interface, and i'm passing it implementation to my util class.
What is advantage of doing this in my way? you have one method which deals with searching in any type collection. and you dont have to create separate methods if you want to search by different field. alll what you need to do is provide different predicate which can be destroyed as soon as it no longer usefull/
if you want to use it, all what you need to do is call method and define tyour predicate
CollectionUtil.find(list, new Predicate<MyObject>{
public boolean contains(T item){
return "John".equals(item.getName());
}
});
Here is a solution using Guava
private boolean checkUserListContainName(List<User> userList, final String targetName){
return FluentIterable.from(userList).anyMatch(new Predicate<User>() {
#Override
public boolean apply(#Nullable User input) {
return input.getName().equals(targetName);
}
});
}
contains method uses equals internally. So you need to override the equals method for your class as per your need.
Btw this does not look syntatically correct:
new Object().setName("John")
If you need to perform this List.contains(Object with field value equal to x) repeatedly, a simple and efficient workaround would be:
List<field obj type> fieldOfInterestValues = new ArrayList<field obj type>;
for(Object obj : List) {
fieldOfInterestValues.add(obj.getFieldOfInterest());
}
Then the List.contains(Object with field value equal to x) would be have the same result as fieldOfInterestValues.contains(x);
Despite JAVA 8 SDK there is a lot of collection tools libraries can help you to work with, for instance:
http://commons.apache.org/proper/commons-collections/
Predicate condition = new Predicate() {
boolean evaluate(Object obj) {
return ((Sample)obj).myField.equals("myVal");
}
};
List result = CollectionUtils.select( list, condition );

What is the cause of this odd behaviour from Hashset in Java? [duplicate]

This question already has answers here:
Java Hashset.contains() produces mysterious result
(3 answers)
HashSet allows duplicates
(6 answers)
HashSet adds duplicate entries despite implementing both hashCode() and equals()
(2 answers)
Closed 3 years ago.
I was messing around trying to understand how HashSets behave and I've run into this problem that I can't get my head around. The 2nd and 3rd dog objects have the same name, and equals() and hashcode() have been overriden to make names mean equality. Despite this, the hashSet still has duplicates, and I can't figure out why.
I reread the data structures chapter of Head First Java but it still suggests that my code should work in theory.
public class DataStructsTests<E> {
HashSet<Dogs> tree = new HashSet<Dogs>();
HashSet<Dogs> treeOwner = new HashSet<Dogs>();
public static void main(String[] args) {
DataStructsTests<String> d = new DataStructsTests<String>();
d.go();
}
public void go() {
Dogs dog = new Dogs("Scout", "a");
tree.add(dog);
treeOwner.add(dog);
Dogs dog2 = new Dogs("Brodie", "b");
tree.add(dog2);
treeOwner.add(dog2);
Dogs dog3 = new Dogs("Brodie", "c");
tree.add(dog3);
treeOwner.add(dog3);
System.out.println(tree);
System.out.println(treeOwner);
System.out.println(dog2.equals(dog3));
System.out.println(dog2.hashCode() + " " + dog3.hashCode());
}
class Dogs {
private String name;
private String ownerName;
public Dogs(String n, String o) {
name = n;
ownerName = o;
}
public boolean equals(Dogs d) {
return name.equals(d.getName());
}
public int hashCode() {
return name.hashCode();
}
public String getName() {
return name;
}
public String toString() {
return name;
}
Running the program returns this:
[Brodie, Brodie, Scout]
[Brodie, Brodie, Scout]
true
1998211617 1998211617
Even though equals() returns true and the hashcodes are the same, the duplicates still remain.
Edit: Turns out the problem was that I hadn't overridden the equals() method properly as I used Dog rather than Object.
equals takes an object of type Object, which is the one being called by HashSet. You need something like:
#Override
public boolean equals(Object d) {
if (! d instanceof Dogs){
return false;
}
return name.equals(((Dogs) d).getName());
}
Here are the components of this answer:
public boolean equals(Object d) - equals, at least the version inherited from Object, is defined to take Objects, so to override it, you must also take an Object.
#Override - tells the compiler to warn you if you make a mistake like you made in your question.
d instanceof Dogs - checks if the Object fed in is even a Dogs in the first place.
((Dogs) d).getName() - The reason for the cast to Dogs is because d is now being passed in as an Object, so you won't automatically gain access to Dogs's methods unless you explicitly say that you want to view the Object as a Dogs.
And one final note: the general convention in Java is to name classes in the singular, unless there is some reason to believe that each instance will be multiple somethings. This is to avoid ambiguity. Dog d makes clear what d is; it is clearly a Dog. What, exactly, is a Dogs d? Is d many dogs, and they just don't have their own object type? It becomes a little ambiguous.

Is overriding hashCode and equals a necessity when adding non-primitive objects to a TreeSet?

I'm following a tutorial to better understand Natural Ordering, using TreeSet and the Comparable interface.
The tutorial tells me that, to add non-primitive custom objects to Sets, I need to implement equals() and hashCode(). However, even without implementing these methods I'm able to compile and run the code (as below). I am using IntelliJ with Java 8.
Is overriding equals() and hashCode() absolutely necessary when working with TreeSets (SortedSet interface) and natural ordering?
class My_Person implements Comparable<My_Person>{
private String name;
public My_Person(String name) {
this.name = name;
}
public String toString() {
return name;
}
// #Override
// public boolean equals(Object o) {
// if (this == o)
// return true;
// if (o == null || getClass() != o.getClass())
// return false;
// My_Person my_person = (My_Person) o;
// return Objects.equals(name, my_person.name);
// }
//
// #Override
// public int hashCode() {
// return Objects.hash(name);
// }
#Override
public int compareTo(My_Person person) {
return name.compareTo(person.name);
}
}
public class NaturalOrdering {
public static void main(String[] args) {
List<My_Person> list = new ArrayList<>();
Set<My_Person> set = new TreeSet<>();
addElement(list);
addElement(set);
Collections.sort(list);
showElement(list);
System.out.println("\n");
showElement(set);
}
private static void addElement(Collection<My_Person> collection) {
collection.add(new My_Person("Joe"));
collection.add(new My_Person("Sue"));
collection.add(new My_Person("Juliet"));
collection.add(new My_Person("Clare"));
collection.add(new My_Person("Mike"));
}
private static void showElement(Collection<My_Person> collection) {
for(My_Person element: collection) {
System.out.println(element);
}
}
}
This depends on your requirements for equality. If you don't override equals and hashCode, two objects are defined as equal if and only if they are identical (i.e. the same object). If you need some other definition for equality you must override the methods.
It is not "absolutely necessary", but then you may get unexpected/wrong output if you don't do so. So if you don't override them, it may still work sometimes, but it may fail too. So just to be safe, better override it.
It will fail if you need to check equality. But if you are only concerned with sorting of the stored objects as per your own defined logic in compareTo method, then I think there is no need to override equals or hashcode.
Its not required by TreeSet or for that matter any Set. But Set is by nature collection of unique objects. For primitive types, Java has way to know whether two objects are same or not. For non-primitive user has to tell Java on how to know whether two objects are same or not and way is to override equals and hashcode methods which are invoked by Set#add method to determine the object being added already exists in the set or not. So if you need a functional unique objects in your Set, you should implement equals and hashcode methods. Hope this helps. This is a good read on the same topic.
You have the source code for TreeSet (and TreeMap which TreeSet uses under the hood). You can clearly see that TreeSet (and TreeMap) rely on compareTo() and not hashCode().
On the other hand, the HashSet (and HashMap which HashSet uses under the hood) does use hashCode() and equals(). No big surprise, considering that they are named HashSet and HashMap.

ArrayList not using the overridden equals

I'm having a problem with getting an ArrayList to correctly use an overriden equals. the problem is that I'm trying to use the equals to only test for a single key field, and using ArrayList.contains() to test for the existence of an object with the correct field. Here is an example
public class TestClass {
private static class InnerClass{
private final String testKey;
//data and such
InnerClass(String testKey, int dataStuff) {
this.testKey =testKey;
//etc
}
#Override
public boolean equals (Object in) {
System.out.println("reached here");
if(in == null) {
return false;
}else if( in instanceof String) {
String inString = (String) in;
return testKey == null ? false : testKey.equals(inString);
}else {
return false;
}
}
}
public static void main(String[] args) {
ArrayList<InnerClass> objectList = new ArrayList<InnerClass>();
//add some entries
objectList.add(new InnerClass("UNIQUE ID1", 42));
System.out.println( objectList.contains("UNIQUE ID1"));
}
}
What worries me is that not only am I getting false on the output, but I'm also not getting the "reached here" output.
Does anyone have any ideas why this override is being completely ignored? Is there some subtlety with overrides and inner classes I don't know of?
Edit:
Having problems with the site so I cant seem to mark the answered.
Thanks for the quick response: yes an oversight on my part that it is the String .equals thta is called, not my custom one. I guess it's old fashioned checks for now
If you check sources of ArrayList, you will see that it calls equals of other object. In your case it will call equals of String "UNIQUE ID1" which will check that other object is not of type String and just returns false:
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
...
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
...
return -1;
}
For your case call contains with InnerClass that only contains id:
objectList.contains(new InnerClass("UNIQUE ID1"))
Don't forget to implement equals for InnerClass which compares id only.
According to the JavaDoc of List.contains(o), it is defined to return true
if and only if this list contains at least one element e such that (o==null ? e==null : o.equals(e)).
Note that this definition calls equals on o, which is the parameter and not the element that is in the List.
Therefore String.equals() will be called and not InnerClass.equals().
Also note that the contract for Object.equals() states that
It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
But you violate this constraint, since new TestClass("foo", 1).equals("foo") returns true but "foo".equals(new TestClass("foo", 1)) will always return false.
Unfortunately this means that your use case (a custom class that can be equal to another standard class) can not be implemented in a completely conforming way.
If you still want to do something like this, you'll have to read the specification (and sometimes the implementation) of all your collection classes very carefully and check for pitfalls such as this.
You're invoking contains with an argument that's a String and not an InnerClass:
System.out.println( objectList.contains("UNIQUE ID1"))
In my JDK:
public class ArrayList {
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
// omitted for brevity - aix
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i])) // <<<<<<<<<<<<<<<<<<<<<<
return i;
}
return -1;
}
}
Note how indexOf calls o.equals(). In your case, o is a String, so your objectList.contains will be using String.equals and not InnerClass.equals.
Generally, you need to also override hashCode() but this is not the main problem here. You are having an asymmetric equals(..) method. The docs make it clear that it should be symmetric:
It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
And what you observe is an unexpected behaviour due to broken contract.
Create an utility method that iterates all items and verifies with equals(..) on the string:
public static boolean containsString(List<InnerClass> items, String str) {
for (InnerClass item : items) {
if (item.getTestKey().equals(str)) {
return true;
}
}
return false;
}
You can do a similar thing with guava's Iterables.any(..) method:
final String str = "Foo";
boolean contains = Iterables.any(items, new Predicate<InnerClass>() {
#Override
public boolean apply(InnerClass input){
return input.getTestKey().equals(str);
}
}
Your equals implementation is wrong. Your in parameter should not be a String. It should be an InnerClass.
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof InnerClass) return false;
InnerClass that = (InnerClass)o;
// check for null keys if you need to
return this.testKey.equals(that.testKey);
}
(Note that instanceof null returns false, so you don't need to check for null first).
You would then test for existence of an equivalent object in your list using:
objectList.contains(new InnerClass("UNIQUE ID1"));
But if you really want to check for InnerClass by String key, why not use Map<String,InnerClass> instead?
Although not answering your question, many Collections use hashcode(). You should override that too to "agree" with equals().
Actually, you should always implement both equals and hashcode together, and they should always be consistent with each other. As the javadoc for Object.equals() states:
Note that it is generally necessary to
override the hashCode method whenever
this method is overridden, so as to
maintain the general contract for the
hashCode method, which states that
equal objects must have equal hash
codes.
Specifically, many Collections rely on this contract being upheld - behaviour is undefined otherwise.
There are a few issues with your code. My suggestion would be to avoid overriding the equals entirely if you are not familiar with it and extend it into a new implementation like so...
class MyCustomArrayList extends ArrayList<InnerClass>{
public boolean containsString(String value){
for(InnerClass item : this){
if (item.getString().equals(value){
return true;
}
}
return false;
}
}
Then you can do something like
List myList = new MyCustomArrayList()
myList.containsString("some string");
I suggest this because if you override the equals should also override the hashCode and it seems you are lacking a little knowledge in this area - so i would just avoid it.
Also, the contains method calls the equals method which is why you are seeing the "reached here". Again if you don't understand the call flow i would just avoid it.
in the other way, your equal method gets called if you change your code as follows. hope this clears the concept.
package com.test;
import java.util.ArrayList;
import java.util.List;
public class TestClass {
private static class InnerClass{
private final String testKey;
//data and such
InnerClass(String testKey, int dataStuff) {
this.testKey =testKey;
//etc
}
#Override
public boolean equals (Object in1) {
System.out.println("reached here");
if(in1 == null) {
return false;
}else if( in1 instanceof InnerClass) {
return ((InnerClass) this).testKey == null ? false : ((InnerClass) this).testKey.equals(((InnerClass) in1).testKey);
}else {
return false;
}
}
}
public static void main(String[] args) {
ArrayList<InnerClass> objectList = new ArrayList<InnerClass>();
InnerClass in1 = new InnerClass("UNIQUE ID1", 42);
InnerClass in2 = new InnerClass("UNIQUE ID1", 42);
//add some entries
objectList.add(in1);
System.out.println( objectList.contains(in2));
}
}
As many posts have said, the problem is that list.indexOf(obj) function calls "equals" of the obj, not the items on the list.
I had the same problem and "contains()" didn't satisfy me, as I need to know where is the element!. My aproach is to create an empty element with just the parameter to compare, and then call indexOf.
Implement a function like this,
public static InnerClass empty(String testKey) {
InnerClass in = new InnerClass();
in.testKey =testKey;
return in;
}
And then, call indexOf like this:
ind position = list.indexOf(InnerClass.empty(key));
There are two errors in your code.
First:
The "contains" method called on "objectList" object should pass a new InnerClass object as the parameter.
Second:
The equals method (should accept the parameter as Object, and is correct) should handle the code properly according to the received object.
Like this:
#Override
public boolean equals (Object in) {
System.out.println("reached here");
if(in == null) {
return false;
}else if( in instanceof InnerClass) {
String inString = ((InnerClass)in).testKey;
return testKey == null ? false : testKey.equals(inString);
}else {
return false;
}
}
This post was first written before Java 8 was available but now that it's 2017 instead of using the List.containts(...) method you can use the new Java 8 way like this:
System.out.println(objectList.stream().filter(obj -> obj.getTestKey().equals("UNIQUE ID1")).findAny().isPresent());
And give your TestClass a getter for your testKey field:
public String getTestKey() {
return testKey;
}
The benefit of this approach is that you don't have to modify the equals or hash method and you'll look like a boss to your peers!

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

This question already has answers here:
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.

Categories