I am trying to achieve an util as this in Spring Boot:
public static boolean isAllEmptyOrNull(Collection... collectionList) {
for (Collection collection : collectionList) {
if (!Collections.isEmpty(collection)) {
return false;
}
}
return true;
}
so I can handle cases as:
isAllEmptyOrNull(listOfCat);
isAllEmptyOrNull(listOfDog, mapOfStringToString);
isAllEmptyOrNull(listOfDog, listOfCat);
isAllEmptyOrNull(listOfDog, listOfCat, mapOfStringToList, mapOfStringToMap);
Any help will be sincerely appreciated :)
Updated 2018-12-06
Thanks for the help of #Deadpool, my solution turns out:
public static boolean isAllCollectionEmptyOrNull(Collection... collections) {
for (Collection collection : collections) {
if (!Collections.isEmpty(collection)) {
return false;
}
}
return true;
}
public static boolean isAllMapEmptyOrNull(Map... maps) {
for (Map map : maps) {
if (!Collections.isEmpty(map)) {
return false;
}
}
return true;
}
Of course, you can use stream and method overloading as nullpointer does.
No. You cannot create it as generic as you are looking for since a Map is not a Collection.
And of course Collection... collectionList signifies var args for Collection type.
The only way would be to break them into two separate stubs as :
public static boolean isAllEmptyOrNull(Collection... collectionList) {
return Arrays.stream(collectionList).allMatch(Collection::isEmpty);
}
public static boolean isAllEmptyOrNull(Map... maps) {
return Arrays.stream(maps).allMatch(Map::isEmpty);
}
You can have two different util methods one for to check Collection objects and another one for Map objects, since Map is not child of Collection interface
public static boolean isAllEmptyOrNull(Collection... collectionList) {
return Arrays.stream(collectionList).anyMatch(item->item==null || item.isEmpty());
}
public static boolean isAllEmptyOrNull(Map... maps) {
return Arrays.stream(maps).anyMatch(item->item==null || item.isEmpty());
}
To check all objects null or empty
public static boolean isAllEmptyOrNull(Collection... collectionList) {
return Arrays.stream(collectionList).allMatch(item->item==null || item.isEmpty());
}
public static boolean isAllEmptyOrNull(Map... maps) {
return Arrays.stream(maps).allMatch(item->item==null || item.isEmpty());
}
You can try this:
public static boolean isAllEmptyOrNull(Collection... collectionList) {
return Arrays.stream(collectionList).anyMatch(Collection::isEmpty);
}
I have a class that is mapped from a xml. To make it simple, let's imagine this class is something like:
class Employee implements EmployeeIF {
Map<AttributeIF,Object> attribute = new HashMap<>();
#Override
public Map<AttributeIF,Object> getAttributes() { return attribute; }
}
This is something I cannot change.
Now, the existing code is full of methods like:
public int getSalary(EmployeeIF employee) {
for(Entry<AttributeIF,Object> entry : employee.getAttributes()) {
if(entry.getKey().getName().equals("salary")) return (Integer)entry.getValue();
}
return 0;
}
public int getAddress(EmployeeIF employee) {
for(Entry<AttributeIF,Object> entry : employee.getAttributes()) {
if(entry.getKey().getName().equals("address")) return (String)entry.getValue();
}
return "";
}
... and so on. Surely you got the idea.
I need to include a new method to return a new attribute from the employee, but as I feel this is horrible to mantain, I refuse to just add a new method there.
I am thinking on using the action pattern to somehow avoiding at least repeating againg and again the for loop but I have to say that I cannot find a smart solution for this.
What would be your choices?
Thanks,
Dani.
P.D Yes I tried something like
private Object getAttribute(EmployeeIF employee, String attribute)
Here is a tiny example how you could get, based on a object as key that you don´t have, the value.
public class TestObject {
public String val;
public TestObject(String val) {
this.val = val;
}
public static TestObject createDummy(String val) {
return new TestObject(val);
}
#Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof TestObject)) return false;
return ((TestObject)obj).val.equals(this.val);
}
#Override
public int hashCode() {
System.out.println("THIS ONE IS IMPORTANT");
return val.hashCode();
}
}
public class TestMap {
public Map<TestObject, String> map = new HashMap<>();
public String get(String keyVal) {
return map.get(TestObject.createDummy(keyVal));
}
public static void main(String[] args) {
TestMap map = new TestMap();
TestObject o1 = new TestObject("A");
map.map.put(o1,"B");
TestObject o2 = new TestObject("B");
map.map.put(o2,"C");
TestObject o3 = new TestObject("C");
map.map.put(o3,"D");
System.out.println(map.get("B"));
}
}
The Key to it, is to override equals and hashCode in your AttributeIF class. So in case you are passing a dummy object of they AttributeIF you do want to have your map needs to identify this dummy object to be equal with the instance of an theoretcly "equal" key object instance inside your Map.
I have a generic class with a generic list in it. I want to ensure that the generic list only contains unique classes.
What I have done so far is to compare the class names with reflection (getClass()). But I think that's not a clean solution. Are there any better practices to check?
public class MyGenericClass<T extends MyGenericClass.MyInterface> {
private List<T> members = new ArrayList<>(0);
public void add(T t) {
final boolean[] classInMembers = {false};
members.forEach(member -> {
if (member.getClass().getName().equals(t.getClass().getName())) {
classInMembers[0] = true;
}
});
if (!classInMembers[0]) {
members.add(t);
}
}
public interface MyInterface {
void doSomething(String text);
}
}
public class Main {
public static void main(String[] args) {
MyGenericClass<MyGenericClass.MyInterface> myGenericClass = new MyGenericClass<>();
myGenericClass.add(new Performer1());
myGenericClass.add(new Performer2());
myGenericClass.add(new Performer3());
myGenericClass.add(new Performer3()); // should not be inserted!
}
private static class Performer1 implements MyGenericClass.MyInterface {
#Override
public void doSomething(String text) {
text = "Hi, I am performer 1!";
}
}
private static class Performer2 implements MyGenericClass.MyInterface {
#Override
public void doSomething(String text) {
text = "Hi, I am performer 2!";
}
}
private static class Performer3 implements MyGenericClass.MyInterface {
#Override
public void doSomething(String text) {
text = "Hi, I am performer 3!";
}
}
}
You could subclass a java.util.Set interface implementation. It will likely be easiest to subclass java.util.AbstractSet.
By default 'Set' will compare objects by their .equals() method - In your case, this is not sufficient. You will need to override the contains method to ensure that only instances of a unique class are added.
In your overrideen contains, it's probably the same / easier to compare class instances rather than their stringified package name
I.e. use a.getClass() == b.getClass(), rather than a.getClass().getName()
Don't use a List, use a java.util.Set instead.
A collection that contains no duplicate elements. More formally, sets contain no pair of elements e1 and e2 such that e1.equals(e2), and at most one null element.
If the iteration order is important or if you want to use a custom Comparator, the TreeSet implementation can be used:
A NavigableSet implementation based on a TreeMap. The elements are ordered using their natural ordering, or by a Comparator provided at set creation time, depending on which constructor is used.
Example of a Set using a Comparator:
class MyComparator implements Comparator<Object> {
#Override
public int compare(Object e1, Object e2) {
if (e1.getClass() == e2.getClass())
return 0;
//if you wish to have some extra sort order
return e1.getClass().getName().compareTo(e2.getClass().getName());
}
}
. . .
Set mySet = new TreeSet<Object>(new MyComparator());
mySet.add(new Object());
mySet.add(new Object());//same class already in set
mySet.add("wtf");
//mySet.size() is now 2 - the second "new Object()" was not inserted due to the comparator check
Why so complicated?
public class Main {
public static void main(String[] args) {
final Class<?> helloClass = "Hello".getClass();
final Class<?> worldClass = "World".getClass();
final Class<?> intClass = Integer.class;
System.out.println(helloClass.equals(worldClass)); // -> true
System.out.println(helloClass.equals(intClass)); // -> false
}
}
You could maintain a roster of members in a Set.
public static class MyGenericClass<T extends MyGenericClass.MyInterface> {
private List<T> members = new ArrayList<>(0);
// Add this.
private Set<Class<?>> roster = new HashSet<>();
public void add(T t) {
if (!roster.contains(t.getClass())) {
members.add(t);
roster.add(t.getClass());
}
}
private void soundOff() {
for (T t : members) {
t.doSomething();
}
}
public interface MyInterface {
void doSomething();
}
}
private static class Performer implements MyGenericClass.MyInterface {
final int n;
public Performer(int n) {
this.n = n;
}
#Override
public void doSomething() {
System.out.println("Hi, I am a " + this.getClass().getSimpleName() + "(" + n + ")");
}
}
private static class Performer1 extends Performer {
public Performer1(int n) {
super(n);
}
}
private static class Performer2 extends Performer {
public Performer2(int n) {
super(n);
}
}
private static class Performer3 extends Performer {
public Performer3(int n) {
super(n);
}
}
public void test() {
MyGenericClass<MyGenericClass.MyInterface> myGenericClass = new MyGenericClass<>();
myGenericClass.add(new Performer1(1));
myGenericClass.add(new Performer2(2));
myGenericClass.add(new Performer3(3));
myGenericClass.add(new Performer3(4)); // should not be inserted!
myGenericClass.soundOff();
}
You could implement a Wrapper which provides the necessary comparison and add the wrapped instance to the set. This way you don't have to override equals and hashcode in your concrete Performer classes and you don't have to subclass a concrete Set implementation (which you are coupled to. When you subclass a HashSet, you have to use that concrete class. But what if you want to use a LinkedHashSet at some point? You have to override LinkedHashSet as well) , which may be fragile since you have to make sure that the overridden method is consistent with the rest of the class.
class MyGenericClass<T extends MyInterface> {
private Set<ClassCompareWrapper<T>> members = new HashSet<>();
public void add(T t) {
members.add(new ClassCompareWrapper<T>(t));
}
}
class ClassCompareWrapper<T> {
T t;
public ClassCompareWrapper(T t) {
this.t = t;
}
#Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof ClassCompareWrapper))
return false;
ClassCompareWrapper<?> that = (ClassCompareWrapper<?>) o;
return Objects.equals(t.getClass(), that.t.getClass());
}
#Override
public int hashCode() {
return Objects.hash(t.getClass());
}
#Override
public String toString() {
return "Wrapper{" +
"t=" + t +
'}';
}
}
Here are a few other ideas.
Using streams:
public void add(T t) {
if (!members.stream().anyMatch(m -> m.getClass() == t.getClass())) {
members.add(t);
}
}
Using AbstractSet and HashMap:
class ClassSet<E> extends AbstractSet<E> {
private final Map<Class<?>, E> map = new HashMap<>();
#Override
public boolean add(E e) {
// this can be
// return map.putIfAbsent(e.getClass(), e) != null;
// in Java 8
Class<?> clazz = e.getClass();
if (map.containsKey(clazz)) {
return false;
} else {
map.put(clazz, e);
return true;
}
}
#Override
public boolean remove(Object o) {
return map.remove(o.getClass()) != null;
}
#Override
public boolean contains(Object o) {
return map.containsKey(o.getClass());
}
#Override
public int size() {
return map.size();
}
#Override
public Iterator<E> iterator() {
return map.values().iterator();
}
}
A HashMap could also be used without wrapping it in a Set. The Set interface is defined around equals and hashCode, so any implementation which deviates from this is technically non-contractual. Additionally, you might want to use LinkedHashMap if the values are iterated often.
I have a method which looks like this:
void foo (List<String> list, ...) {
...
for (String s : list) { // this is the only place where `list` is used
...
}
...
}
the exact same code would work if I replace List<String> list with String[] list, however, to avoid spaghetti code, I keep the single method, and when I need to call it on an array a, I do it like this: foo(Arrays.asList(a)).
I wonder if this is The Right Way.
Specifically,
What is the overhead of Arrays.asList()?
Is there a way to write a method which would accept both arrays and lists, just like the for loop does?
Thanks!
Arrays.asList() has a small overhead. There is no real way to implement one method for both List and arrays.
But you can do the following:
void foo (List<String> list, ...) {
...
for (String s : list) { // this is the only place where *list* is used
...
}
...
}
void foo (String[] arr, ...) {
if ( arr != null ) {
foo(Arrays.asList(arr),...);
}
}
From the source code of openjdk, Arrays.asList:
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
furthermore:
ArrayList(E[] array) {
if (array==null)
throw new NullPointerException();
a = array;
}
So basically all that happens in an assignment, so the overhead should be negligible.
The overhead is that it converts an array to a list--how it does so would be implementation-dependent, it only needs to fulfill the contract.
IMO you should write two methods if you're concerned about the potential runtime overhead: that is the nature of Java; methods have type signatures, and they must be obeyed.
Do avoid this I just use and allow Lists, Sets and Maps (like Joshua Bloch told us). There is no way to merge both "collection types".
An alternative is to use guava (Iterators/Iteratables). So you can iterarte over your collections without a deep copy of them.
Good question.
This is a very common case, and is often dealt with by writing two separate methods. However code duplication is really a bad idea, and whenever you find yourself duplicating code, you should start looking for opportunities to factor your code better. (As you are doing right now!)
Now if you look into the source of java.util.Arrays, you will notice that Arrays.asList retruns an instance of a private inner class Arrays.ArrayList which is just a thin wrapper over plain arrays, and delegates all relevant method calls to it. (This is known as a projection or view of a data structure.) Therefore the overhead incurred is insignificant (unless you are striving to extract every last bit of performance), and in my opinion, you should go ahead and use this method without worrying about performance.
The solution I personally use is as follows.
I have a class named RichIterable in my personal utils. As the name indicates the class wraps over Iterable and provides some additional useful methods not already present. The class also has a factory method that creates an RichIterable from an array. Here is the class definition.
public class RichIterable<A> implements Iterable<A> {
private Iterable<A> xs;
private RichIterable(Iterable<A> xs) {
this.xs = xs;
}
public static <A> RichIterable<A> from(Iterable<A> xs) {
if (xs instanceof RichIterable) {
return (RichIterable<A>) xs;
} else {
return new RichIterable<A>(xs);
}
}
public static <A> RichIterable<A> from(final Enumeration<A> xs) {
Iterable<A> iterable = new Iterable<A>() {
#Override
public Iterator<A> iterator() {
return new Iterator<A>() {
#Override
public boolean hasNext() {
return xs.hasMoreElements();
}
#Override
public A next() {
return xs.nextElement();
}
#Override
public void remove() {
throw new UnsupportedOperationException(
"Cannot remove an element from an enumeration.");
}
};
}
};
return RichIterable.from(iterable);
}
public static <A> RichIterable<A> from(final A[] xs) {
Iterable<A> iterable = new Iterable<A>() {
#Override
public Iterator<A> iterator() {
return new Iterator<A>() {
private int i = 0;
#Override
public boolean hasNext() {
return i < xs.length;
}
#Override
public A next() {
A x = xs[i];
i++;
return x;
}
#Override
public void remove() {
throw new UnsupportedOperationException(
"Cannot remove an element from an array.");
}
};
}
};
return RichIterable.from(iterable);
}
public boolean isEmpty() {
if (xs instanceof Collection) {
return ((Collection) xs).isEmpty();
}
for (A x : xs) {
return false;
}
return true;
}
public int size() {
if (xs instanceof Collection) {
return ((Collection) xs).size();
}
int size = 0;
for (A x : xs) {
size++;
}
return size;
}
public ArrayList<A> toArrayList() {
ArrayList<A> ys = new ArrayList<A>();
for (A x : xs) {
ys.add(x);
}
return ys;
}
public <B> RichIterable<B> map(F1<A, B> f) {
List<B> ys = new ArrayList<B>();
for (A x : xs) {
ys.add(f.apply(x));
}
return RichIterable.from(ys);
}
public RichIterable<A> filter(F1<A, Boolean> pred) {
List<A> ys = new ArrayList<A>();
Arrays.asList();
for (A x : xs) {
if (pred.apply(x)) {
ys.add(x);
}
}
return RichIterable.from(ys);
}
public boolean exists(F1<A, Boolean> pred) {
for (A x : xs) {
if (pred.apply(x)) {
return true;
}
}
return false;
}
public boolean forall(F1<A, Boolean> pred) {
for (A x : xs) {
if (!pred.apply(x)) {
return false;
}
}
return true;
}
public Maybe<A> find(F1<A, Boolean> pred) {
for (A x : xs) {
if (pred.apply(x)) {
return Just.of(x);
}
}
return Nothing.value();
}
public String mkString(String beg, String sep, String end) {
Iterator<A> i = xs.iterator();
if (!i.hasNext()) {
return beg + end;
}
StringBuilder sb = new StringBuilder();
sb.append(beg);
while (true) {
A e = i.next();
sb.append(e.toString());
if (!i.hasNext()) {
return sb.append(end).toString();
}
sb.append(sep);
}
}
public String mkString(String sep) {
return mkString("", sep, "");
}
public String mkString() {
return this.mkString(", ");
}
public Iterable<A> getRaw() {
return xs;
}
#Override
public Iterator<A> iterator() {
return xs.iterator();
}
}
I would like to use case insensitive string as a HashMap key for the following reasons.
During initialization, my program creates HashMap with user defined String
While processing an event (network traffic in my case), I might received String in a different case but I should be able to locate the <key, value> from HashMap ignoring the case I received from traffic.
I've followed this approach
CaseInsensitiveString.java
public final class CaseInsensitiveString {
private String s;
public CaseInsensitiveString(String s) {
if (s == null)
throw new NullPointerException();
this.s = s;
}
public boolean equals(Object o) {
return o instanceof CaseInsensitiveString &&
((CaseInsensitiveString)o).s.equalsIgnoreCase(s);
}
private volatile int hashCode = 0;
public int hashCode() {
if (hashCode == 0)
hashCode = s.toUpperCase().hashCode();
return hashCode;
}
public String toString() {
return s;
}
}
LookupCode.java
node = nodeMap.get(new CaseInsensitiveString(stringFromEvent.toString()));
Because of this, I'm creating a new object of CaseInsensitiveString for every event. So, it might hit performance.
Is there any other way to solve this issue?
Map<String, String> nodeMap =
new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
That's really all you need.
As suggested by Guido García in their answer here:
import java.util.HashMap;
public class CaseInsensitiveMap extends HashMap<String, String> {
#Override
public String put(String key, String value) {
return super.put(key.toLowerCase(), value);
}
// not #Override because that would require the key parameter to be of type Object
public String get(String key) {
return super.get(key.toLowerCase());
}
}
Or
https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/map/CaseInsensitiveMap.html
One approach is to create a custom subclass of the Apache Commons AbstractHashedMap class, overriding the hash and isEqualKeys methods to perform case insensitive hashing and comparison of keys. (Note - I've never tried this myself ...)
This avoids the overhead of creating new objects each time you need to do a map lookup or update. And the common Map operations should O(1) ... just like a regular HashMap.
And if you are prepared to accept the implementation choices they have made, the Apache Commons CaseInsensitiveMap does the work of customizing / specializing AbstractHashedMap for you.
But if O(logN) get and put operations are acceptable, a TreeMap with a case insensitive string comparator is an option; e.g. using String.CASE_INSENSITIVE_ORDER.
And if you don't mind creating a new temporary String object each time you do a put or get, then Vishal's answer is just fine. (Though, I note that you wouldn't be preserving the original case of the keys if you did that ...)
Subclass HashMap and create a version that lower-cases the key on put and get (and probably the other key-oriented methods).
Or composite a HashMap into the new class and delegate everything to the map, but translate the keys.
If you need to keep the original key you could either maintain dual maps, or store the original key along with the value.
Two choices come to my mind:
You could use directly the s.toUpperCase().hashCode(); as the key of the Map.
You could use a TreeMap<String> with a custom Comparator that ignore the case.
Otherwise, if you prefer your solution, instead of defining a new kind of String, I would rather implement a new Map with the required case insensibility functionality.
Wouldn't it be better to "wrap" the String in order to memorize the hashCode. In the normal String class hashCode() is O(N) the first time and then it is O(1) since it is kept for future use.
public class HashWrap {
private final String value;
private final int hash;
public String get() {
return value;
}
public HashWrap(String value) {
this.value = value;
String lc = value.toLowerCase();
this.hash = lc.hashCode();
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof HashWrap) {
HashWrap that = (HashWrap) o;
return value.equalsIgnoreCase(that.value);
} else {
return false;
}
}
#Override
public int hashCode() {
return this.hash;
}
//might want to implement compare too if you want to use with SortedMaps/Sets.
}
This would allow you to use any implementation of Hashtable in java and to have O(1) hasCode().
You can use a HashingStrategy based Map from Eclipse Collections
HashingStrategy<String> hashingStrategy =
HashingStrategies.fromFunction(String::toUpperCase);
MutableMap<String, String> node = HashingStrategyMaps.mutable.of(hashingStrategy);
Note: I am a contributor to Eclipse Collections.
Based on other answers, there are basically two approaches: subclassing HashMap or wrapping String. The first one requires a little more work. In fact, if you want to do it correctly, you must override almost all methods (containsKey, entrySet, get, put, putAll and remove).
Anyway, it has a problem. If you want to avoid future problems, you must specify a Locale in String case operations. So you would create new methods (get(String, Locale), ...). Everything is easier and clearer wrapping String:
public final class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s, Locale locale) {
this.s = s.toUpperCase(locale);
}
// equals, hashCode & toString, no need for memoizing hashCode
}
And well, about your worries on performance: premature optimization is the root of all evil :)
Instead of creating your own class to validate and store case insensitive string as a HashMap key, you can use:
LinkedCaseInsensitiveMap wraps a LinkedHashMap, which is a Map based on a hash table and a linked list. Unlike LinkedHashMap, it doesn't allow null key inserting. LinkedCaseInsensitiveMap preserves the original order as well as the original casing of keys while allowing calling functions like get and remove with any case.
Eg:
Map<String, Integer> linkedHashMap = new LinkedCaseInsensitiveMap<>();
linkedHashMap.put("abc", 1);
linkedHashMap.put("AbC", 2);
System.out.println(linkedHashMap);
Output: {AbC=2}
Mvn Dependency:
Spring Core is a Spring Framework module that also provides utility classes, including LinkedCaseInsensitiveMap.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
CaseInsensitiveMap is a hash-based Map, which converts keys to lower case before they are being added or retrieved. Unlike TreeMap, CaseInsensitiveMap allows null key inserting.
Eg:
Map<String, Integer> commonsHashMap = new CaseInsensitiveMap<>();
commonsHashMap.put("ABC", 1);
commonsHashMap.put("abc", 2);
commonsHashMap.put("aBc", 3);
System.out.println(commonsHashMap);
Output: {abc=3}
Dependency:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
TreeMap is an implementation of NavigableMap, which means that it always sorts the entries after inserting, based on a given Comparator. Also, TreeMap uses a Comparator to find if an inserted key is a duplicate or a new one.
Therefore, if we provide a case-insensitive String Comparator, we'll get a case-insensitive TreeMap.
Eg:
Map<String, Integer> treeMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
treeMap.put("ABC", 1);
treeMap.put("ABc", 2);
treeMap.put("cde", 1);
System.out.println(treeMap);
Output: {ABC=2, cde=1}
You can use CollationKey objects instead of strings:
Locale locale = ...;
Collator collator = Collator.getInstance(locale);
collator.setStrength(Collator.SECONDARY); // Case-insensitive.
collator.setDecomposition(Collator.FULL_DECOMPOSITION);
CollationKey collationKey = collator.getCollationKey(stringKey);
hashMap.put(collationKey, value);
hashMap.get(collationKey);
Use Collator.PRIMARY to ignore accent differences.
The CollationKey API does not guarantee that hashCode() and equals() are implemented, but in practice you'll be using RuleBasedCollationKey, which does implement these. If you're paranoid, you can use a TreeMap instead, which is guaranteed to work at the cost of O(log n) time instead of O(1).
This is an adapter for HashMaps which I implemented for a recent project. Works in a way similart to what #SandyR does, but encapsulates conversion logic so you don't manually convert strings to a wrapper object.
I used Java 8 features but with a few changes, you can adapt it to previous versions. I tested it for most common scenarios, except new Java 8 stream functions.
Basically it wraps a HashMap, directs all functions to it while converting strings to/from a wrapper object. But I had to also adapt KeySet and EntrySet because they forward some functions to the map itself. So I return two new Sets for keys and entries which actually wrap the original keySet() and entrySet().
One note: Java 8 has changed the implementation of putAll method which I could not find an easy way to override. So current implementation may have degraded performance especially if you use putAll() for a large data set.
Please let me know if you find a bug or have suggestions to improve the code.
package webbit.collections;
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class CaseInsensitiveMapAdapter<T> implements Map<String,T>
{
private Map<CaseInsensitiveMapKey,T> map;
private KeySet keySet;
private EntrySet entrySet;
public CaseInsensitiveMapAdapter()
{
}
public CaseInsensitiveMapAdapter(Map<String, T> map)
{
this.map = getMapImplementation();
this.putAll(map);
}
#Override
public int size()
{
return getMap().size();
}
#Override
public boolean isEmpty()
{
return getMap().isEmpty();
}
#Override
public boolean containsKey(Object key)
{
return getMap().containsKey(lookupKey(key));
}
#Override
public boolean containsValue(Object value)
{
return getMap().containsValue(value);
}
#Override
public T get(Object key)
{
return getMap().get(lookupKey(key));
}
#Override
public T put(String key, T value)
{
return getMap().put(lookupKey(key), value);
}
#Override
public T remove(Object key)
{
return getMap().remove(lookupKey(key));
}
/***
* I completely ignore Java 8 implementation and put one by one.This will be slower.
*/
#Override
public void putAll(Map<? extends String, ? extends T> m)
{
for (String key : m.keySet()) {
getMap().put(lookupKey(key),m.get(key));
}
}
#Override
public void clear()
{
getMap().clear();
}
#Override
public Set<String> keySet()
{
if (keySet == null)
keySet = new KeySet(getMap().keySet());
return keySet;
}
#Override
public Collection<T> values()
{
return getMap().values();
}
#Override
public Set<Entry<String, T>> entrySet()
{
if (entrySet == null)
entrySet = new EntrySet(getMap().entrySet());
return entrySet;
}
#Override
public boolean equals(Object o)
{
return getMap().equals(o);
}
#Override
public int hashCode()
{
return getMap().hashCode();
}
#Override
public T getOrDefault(Object key, T defaultValue)
{
return getMap().getOrDefault(lookupKey(key), defaultValue);
}
#Override
public void forEach(final BiConsumer<? super String, ? super T> action)
{
getMap().forEach(new BiConsumer<CaseInsensitiveMapKey, T>()
{
#Override
public void accept(CaseInsensitiveMapKey lookupKey, T t)
{
action.accept(lookupKey.key,t);
}
});
}
#Override
public void replaceAll(final BiFunction<? super String, ? super T, ? extends T> function)
{
getMap().replaceAll(new BiFunction<CaseInsensitiveMapKey, T, T>()
{
#Override
public T apply(CaseInsensitiveMapKey lookupKey, T t)
{
return function.apply(lookupKey.key,t);
}
});
}
#Override
public T putIfAbsent(String key, T value)
{
return getMap().putIfAbsent(lookupKey(key), value);
}
#Override
public boolean remove(Object key, Object value)
{
return getMap().remove(lookupKey(key), value);
}
#Override
public boolean replace(String key, T oldValue, T newValue)
{
return getMap().replace(lookupKey(key), oldValue, newValue);
}
#Override
public T replace(String key, T value)
{
return getMap().replace(lookupKey(key), value);
}
#Override
public T computeIfAbsent(String key, final Function<? super String, ? extends T> mappingFunction)
{
return getMap().computeIfAbsent(lookupKey(key), new Function<CaseInsensitiveMapKey, T>()
{
#Override
public T apply(CaseInsensitiveMapKey lookupKey)
{
return mappingFunction.apply(lookupKey.key);
}
});
}
#Override
public T computeIfPresent(String key, final BiFunction<? super String, ? super T, ? extends T> remappingFunction)
{
return getMap().computeIfPresent(lookupKey(key), new BiFunction<CaseInsensitiveMapKey, T, T>()
{
#Override
public T apply(CaseInsensitiveMapKey lookupKey, T t)
{
return remappingFunction.apply(lookupKey.key, t);
}
});
}
#Override
public T compute(String key, final BiFunction<? super String, ? super T, ? extends T> remappingFunction)
{
return getMap().compute(lookupKey(key), new BiFunction<CaseInsensitiveMapKey, T, T>()
{
#Override
public T apply(CaseInsensitiveMapKey lookupKey, T t)
{
return remappingFunction.apply(lookupKey.key,t);
}
});
}
#Override
public T merge(String key, T value, BiFunction<? super T, ? super T, ? extends T> remappingFunction)
{
return getMap().merge(lookupKey(key), value, remappingFunction);
}
protected Map<CaseInsensitiveMapKey,T> getMapImplementation() {
return new HashMap<>();
}
private Map<CaseInsensitiveMapKey,T> getMap() {
if (map == null)
map = getMapImplementation();
return map;
}
private CaseInsensitiveMapKey lookupKey(Object key)
{
return new CaseInsensitiveMapKey((String)key);
}
public class CaseInsensitiveMapKey {
private String key;
private String lookupKey;
public CaseInsensitiveMapKey(String key)
{
this.key = key;
this.lookupKey = key.toUpperCase();
}
#Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CaseInsensitiveMapKey that = (CaseInsensitiveMapKey) o;
return lookupKey.equals(that.lookupKey);
}
#Override
public int hashCode()
{
return lookupKey.hashCode();
}
}
private class KeySet implements Set<String> {
private Set<CaseInsensitiveMapKey> wrapped;
public KeySet(Set<CaseInsensitiveMapKey> wrapped)
{
this.wrapped = wrapped;
}
private List<String> keyList() {
return stream().collect(Collectors.toList());
}
private Collection<CaseInsensitiveMapKey> mapCollection(Collection<?> c) {
return c.stream().map(it -> lookupKey(it)).collect(Collectors.toList());
}
#Override
public int size()
{
return wrapped.size();
}
#Override
public boolean isEmpty()
{
return wrapped.isEmpty();
}
#Override
public boolean contains(Object o)
{
return wrapped.contains(lookupKey(o));
}
#Override
public Iterator<String> iterator()
{
return keyList().iterator();
}
#Override
public Object[] toArray()
{
return keyList().toArray();
}
#Override
public <T> T[] toArray(T[] a)
{
return keyList().toArray(a);
}
#Override
public boolean add(String s)
{
return wrapped.add(lookupKey(s));
}
#Override
public boolean remove(Object o)
{
return wrapped.remove(lookupKey(o));
}
#Override
public boolean containsAll(Collection<?> c)
{
return keyList().containsAll(c);
}
#Override
public boolean addAll(Collection<? extends String> c)
{
return wrapped.addAll(mapCollection(c));
}
#Override
public boolean retainAll(Collection<?> c)
{
return wrapped.retainAll(mapCollection(c));
}
#Override
public boolean removeAll(Collection<?> c)
{
return wrapped.removeAll(mapCollection(c));
}
#Override
public void clear()
{
wrapped.clear();
}
#Override
public boolean equals(Object o)
{
return wrapped.equals(lookupKey(o));
}
#Override
public int hashCode()
{
return wrapped.hashCode();
}
#Override
public Spliterator<String> spliterator()
{
return keyList().spliterator();
}
#Override
public boolean removeIf(Predicate<? super String> filter)
{
return wrapped.removeIf(new Predicate<CaseInsensitiveMapKey>()
{
#Override
public boolean test(CaseInsensitiveMapKey lookupKey)
{
return filter.test(lookupKey.key);
}
});
}
#Override
public Stream<String> stream()
{
return wrapped.stream().map(it -> it.key);
}
#Override
public Stream<String> parallelStream()
{
return wrapped.stream().map(it -> it.key).parallel();
}
#Override
public void forEach(Consumer<? super String> action)
{
wrapped.forEach(new Consumer<CaseInsensitiveMapKey>()
{
#Override
public void accept(CaseInsensitiveMapKey lookupKey)
{
action.accept(lookupKey.key);
}
});
}
}
private class EntrySet implements Set<Map.Entry<String,T>> {
private Set<Entry<CaseInsensitiveMapKey,T>> wrapped;
public EntrySet(Set<Entry<CaseInsensitiveMapKey,T>> wrapped)
{
this.wrapped = wrapped;
}
private List<Map.Entry<String,T>> keyList() {
return stream().collect(Collectors.toList());
}
private Collection<Entry<CaseInsensitiveMapKey,T>> mapCollection(Collection<?> c) {
return c.stream().map(it -> new CaseInsensitiveEntryAdapter((Entry<String,T>)it)).collect(Collectors.toList());
}
#Override
public int size()
{
return wrapped.size();
}
#Override
public boolean isEmpty()
{
return wrapped.isEmpty();
}
#Override
public boolean contains(Object o)
{
return wrapped.contains(lookupKey(o));
}
#Override
public Iterator<Map.Entry<String,T>> iterator()
{
return keyList().iterator();
}
#Override
public Object[] toArray()
{
return keyList().toArray();
}
#Override
public <T> T[] toArray(T[] a)
{
return keyList().toArray(a);
}
#Override
public boolean add(Entry<String,T> s)
{
return wrapped.add(null );
}
#Override
public boolean remove(Object o)
{
return wrapped.remove(lookupKey(o));
}
#Override
public boolean containsAll(Collection<?> c)
{
return keyList().containsAll(c);
}
#Override
public boolean addAll(Collection<? extends Entry<String,T>> c)
{
return wrapped.addAll(mapCollection(c));
}
#Override
public boolean retainAll(Collection<?> c)
{
return wrapped.retainAll(mapCollection(c));
}
#Override
public boolean removeAll(Collection<?> c)
{
return wrapped.removeAll(mapCollection(c));
}
#Override
public void clear()
{
wrapped.clear();
}
#Override
public boolean equals(Object o)
{
return wrapped.equals(lookupKey(o));
}
#Override
public int hashCode()
{
return wrapped.hashCode();
}
#Override
public Spliterator<Entry<String,T>> spliterator()
{
return keyList().spliterator();
}
#Override
public boolean removeIf(Predicate<? super Entry<String, T>> filter)
{
return wrapped.removeIf(new Predicate<Entry<CaseInsensitiveMapKey, T>>()
{
#Override
public boolean test(Entry<CaseInsensitiveMapKey, T> entry)
{
return filter.test(new FromCaseInsensitiveEntryAdapter(entry));
}
});
}
#Override
public Stream<Entry<String,T>> stream()
{
return wrapped.stream().map(it -> new Entry<String, T>()
{
#Override
public String getKey()
{
return it.getKey().key;
}
#Override
public T getValue()
{
return it.getValue();
}
#Override
public T setValue(T value)
{
return it.setValue(value);
}
});
}
#Override
public Stream<Map.Entry<String,T>> parallelStream()
{
return StreamSupport.stream(spliterator(), true);
}
#Override
public void forEach(Consumer<? super Entry<String, T>> action)
{
wrapped.forEach(new Consumer<Entry<CaseInsensitiveMapKey, T>>()
{
#Override
public void accept(Entry<CaseInsensitiveMapKey, T> entry)
{
action.accept(new FromCaseInsensitiveEntryAdapter(entry));
}
});
}
}
private class EntryAdapter implements Map.Entry<String,T> {
private Entry<String,T> wrapped;
public EntryAdapter(Entry<String, T> wrapped)
{
this.wrapped = wrapped;
}
#Override
public String getKey()
{
return wrapped.getKey();
}
#Override
public T getValue()
{
return wrapped.getValue();
}
#Override
public T setValue(T value)
{
return wrapped.setValue(value);
}
#Override
public boolean equals(Object o)
{
return wrapped.equals(o);
}
#Override
public int hashCode()
{
return wrapped.hashCode();
}
}
private class CaseInsensitiveEntryAdapter implements Map.Entry<CaseInsensitiveMapKey,T> {
private Entry<String,T> wrapped;
public CaseInsensitiveEntryAdapter(Entry<String, T> wrapped)
{
this.wrapped = wrapped;
}
#Override
public CaseInsensitiveMapKey getKey()
{
return lookupKey(wrapped.getKey());
}
#Override
public T getValue()
{
return wrapped.getValue();
}
#Override
public T setValue(T value)
{
return wrapped.setValue(value);
}
}
private class FromCaseInsensitiveEntryAdapter implements Map.Entry<String,T> {
private Entry<CaseInsensitiveMapKey,T> wrapped;
public FromCaseInsensitiveEntryAdapter(Entry<CaseInsensitiveMapKey, T> wrapped)
{
this.wrapped = wrapped;
}
#Override
public String getKey()
{
return wrapped.getKey().key;
}
#Override
public T getValue()
{
return wrapped.getValue();
}
#Override
public T setValue(T value)
{
return wrapped.setValue(value);
}
}
}
Because of this, I'm creating a new object of CaseInsensitiveString for every event. So, it might hit performance.
Creating wrappers or converting key to lower case before lookup both create new objects. Writing your own java.util.Map implementation is the only way to avoid this. It's not too hard, and IMO is worth it. I found the following hash function to work pretty well, up to few hundred keys.
static int ciHashCode(String string)
{
// length and the low 5 bits of hashCode() are case insensitive
return (string.hashCode() & 0x1f)*33 + string.length();
}
I like using ICU4J’s CaseInsensitiveString wrap of the Map key because it takes care of the hash\equals and issue and it works for unicode\i18n.
HashMap<CaseInsensitiveString, String> caseInsensitiveMap = new HashMap<>();
caseInsensitiveMap.put("tschüß", "bye");
caseInsensitiveMap.containsKey("TSCHÜSS"); # true
I find solutions which require you to change the key (e.g., toLowerCase) very unwelcome and solutions which require TreeMap also unwelcome.
Since TreeMap changes the time complexity (compared to other HashMaps), I think it's more viable to simply go with a utility method that is O(n):
public static <T> T getIgnoreCase(Map<String, T> map, String key) {
for(Entry<String, T> entry : map.entrySet()) {
if(entry.getKey().equalsIgnoreCase(key))
return entry.getValue();
}
return null;
}
This is that method. Since the sacrifice to performance (time complexity) looks inevitable, at least this doesn't require you to change the underlying map to suit the lookup.