This question already has answers here:
Case insensitive string as HashMap key
(14 answers)
Closed 6 years ago.
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
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.
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<>();
Related
This question already has answers here:
Why does this .equals() code example return "false"? [duplicate]
(3 answers)
Closed 2 years ago.
Hey i have two java class object, whose key and values are same but when I check ob1.equals(obj2) its return false.
here is the code :
Category expected = new Category("01","lorem","custom");
ResponseEntity<List<LinkedHashMap>> response = restTemplate.exchange("/api/categories", HttpMethod.GET,
null, new ParameterizedTypeReference<List<LinkedHashMap>>() {});
LinkedHashMap result = response.getBody().get(0); // which is same as expected object
//check if equals
private boolean areEqual(LinkedHashMap result, Category expected) {
String catId = (String) obj.get("category_id"); //is 01
String name = (String) obj.get("category_name"); // is lorem
String sec = (String) obj.get("section_name"); // is custom
DefaultCategory temp = new Category(catId, name, sec);
return temp.equals(expected); //<--------- returning false, even they are equal
}
The api return this category
#GetMapping("categories")
public ResponseEntity<List<Category>> getDefaultCategories() {
List<Category> categories = new ArrayList();
categories.add(new Category("01","lorem","custom"));
return new ResponseEntity<>(categories, HttpStatus.OK);
}
Standard .equals(...) checks two objects are same instance or not. If you want to compare two objects with their fields. You can override equals method like below.
public class DefaultCategory {
private String catId;
private String name;
private String sec;
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DefaultCategory)) return false;
DefaultCategory other = (DefaultCategory) o;
if(!Objects.equals(other.catId, catId)) return false;
if(!Objects.equals(other.name, name)) return false;
if(!Objects.equals(other.sec, sec)) return false;
return true;
}
}
Because expected is a child class and temp is a super class or vice versa. You are comparing two different objects(types). Cast you temp to Category and you will be fine. Or override your equals to allow mixed type comparison.
I know that the below code gives the index of that particular element in java.
List<String> list = new ArrayList<>();
list .add("100");
Log.d("TAG",String.valueOf(list.indexOf("300")));
But how to get the index of an element while using a helper Class?
List<HelperClass> Arraylist= new ArrayList<>();
Arraylist.add(new HelperClass(name, email, phoneno));
Log.d("TAG", String.valueOf(new HelperClass(Arraylist.indexOf(name,email,phoneno))));
I searched everywhere for this but couldn't find. Can someone tell me how to find index of a particular item in arraylist while using modal to add data?
Obviously what I have tried is wrong and it shows red line under the whole line but I just typed that code for your understanding of what I want to achieve. Can someone give me a way please?
Helper
#Override
public int hashCode() {
int result = getName() != null ? getName().hashCode() : 0;
result = 31 * result + (Email != null ? Emaail.hashCode() : 0);
result = 31 * result + (PhoneNo!= null ? PhoneNo.hashCode() : 0);
return result;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Helper)) return false;
Helperthat = (Helper) o;
if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null)
return false;
if (Email != null ? !Email.equals(that.Email) : that.Email != null)
return false;
if (PhoneNo != null ? !PhoneNo.equals(that.PhoneNo) : that.PhoneNo != null)
return false;
}
ArrayList#indexOf uses the Object#equals comparison method.
If you want to be able to lookup a HelperClass instance inside a Collection, you need to provide your own, overridden, equals method, and possibly also the hashCode one, for use with other, specific, Collection implementations (Map, Set, etc.).
class HelperClass {
...
#Override
public boolean equals(final Object object) {
if (object == this) {
return true;
}
if (!(object instance of HelperClass)) {
return false;
}
final HelperClass other = (HelperClass) object;
return name.equals(other.name) &&
email.equals(other.email) &&
phone.equals(other.phone);
}
}
You obviously need to have an appropriate HelperClass instance to find a match.
final String name = "Name";
final String email = "Email";
final String phone = "Phone";
final HelperClass first = new HelperClass(name, email, phone);
final HelperClass second = new HelperClass(name, email, phone);
final List<HelperClass> helpers = new ArrayList<>(8);
helpers.add(first);
final int index = helpers.indexOf(second); // index = 0
indexOf requires the object as input. If it does not find the object you are passing in, it will return -1. You need to pass the object whose location in the arraylist you are looking for as the input into the indexOf function.
Solution :
create a HelperClass to pass into the indexOf method:
.indexOf(new HelperClass(name, email, phoneno));
However that change by itself will still return -1. See the api doc for indexOf:
public int indexOf(Object o)
Returns the index of the first occurrence of the specified element in
this list, or -1 if this list does not contain the element. More
formally, returns the lowest index i such that (o==null ? get(i)==null
: o.equals(get(i))), or -1 if there is no such index.
It's using equals to decide whether it's found a match. You should have overridden the equals method on your HelperClass class, so it's using the default implementation in java.lang.Object, which compares the references, and only returns true if the two references HelperClass to the same object.
Override equals and hashcode on your HelperClass class, like:
#Override public boolean equals(Object other) {
if (!(other instanceof HelperClass)) {
return false;
}
HelperClass otherHelperClass = (HelperClass)other;
return otherHelperClass.x == this.x && otherHelperClass.y == this.y;
}
#Override public int hashCode() {
return x + y; // same values should hash to the same number
}
I have a Map in Java like so,
private HashMap<String, Object[][]> theMap;
Where the key is a String and the entry is going to be something along the line of,
theMap = new HashMap<>();
Object[][] theData = {
{Boolean.FALSE, "Text"}
};
theMap.put("Key1", theData);
Somewhere along the line I would like to check if an entry in the map is equivalent to another object. Currently I am doing it like this,
Object[][] tempData = {
{Boolean.FALSE, "Text"}
};
for(Object key: entries.keySet()) {
if(entries.get(key).equals(tempData)) {
entries.remove(key);
}
}
And it is not working.
I would prefer the comparison to be done with an object rather than with another map. I'm wondering what I'm doing wrong with this comparison here?
The reason you are not getting equality is that arrays inherit Object#equals() which is based on identity, not equality of contents. You could consider using java.util.Arrays.deepEquals(Object[], Object[]) to compare.
That is the answer to the immediate question. However, using a 2-dimensional array of Object to hold a boolean and a String is really bad code smell and indicates you need to encapsulate what you are putting in the array.
Identity vs Equivalence
Please make sure that you understand that by default the equals() method of Object checks on whether two object references are referring to the same object (identity), which is not what your code is checking.
Instead, your code is checking whether the two objects (the values you put on the map) are having the same value (equivalence).
Here are two articles about this topic:
What is the difference between identity and equality in OOP?
Overriding equals method in Java
In this particular problem of yours, I think the solution involves two steps:
Your tempData and theData does not seems to be an array
of elements of the same type (it does not appear to be a 2-dimensional
array either). Instead, it contains a Boolean value and then a
String value. In this case, I think you really should think
through what this thingy is and design a class for it (I am showing
an example below)
The class should override the equals() (and hashCode()) methods
so that you can use its equals() for equivalence checking.
Note also that your IDE (e.g. Eclipse) probably can generate a template for equals() and hashCode() for you.
Example: (here I assume your Boolean represents a condition, and your String represents a message)
class MyRecord {
private Boolean condition;
private String message;
public Boolean getCondition() {
return condition;
}
public void setCondition(Boolean condition) {
this.condition = condition;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((condition == null) ? 0 : condition.hashCode());
result = prime * result
+ ((message == null) ? 0 : message.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MyRecord other = (MyRecord) obj;
if (condition == null) {
if (other.condition != null)
return false;
} else if (!condition.equals(other.condition))
return false;
if (message == null) {
if (other.message != null)
return false;
} else if (!message.equals(other.message))
return false;
return true;
}
}
I have a class like this:
public static class TiposDeHistorial
{
String CODIGO, TIPO;
public TiposDeHistorial()
{
}
public String getCODIGO()
{
return CODIGO;
}
public void setCODIGO(String CODIGO)
{
this.CODIGO = CODIGO;
}
public String getTIPO()
{
return TIPO;
}
public void setTIPO(String TIPO)
{
this.TIPO = TIPO;
}
}
and a list of it:
ArrayList<TiposDeHistorial> tiposHistorial;
So my question is: can I use tiposHistorial.contains(...) to search in a specific array field, CODIGO or TIPO, for example?
First of, you do not have an array but an ArrayList.
The contains method on a List operates with the equals method of it's stored elements (TiposDeHistorial in your case). Therefore the answer to your question is no.
Trying something like tiposHistorial.contains("a") will not work as there is a type mismatch: your list is of type TiposDeHistorial while you try to check for an element of String.
If you are using Java 8 you can use following code:
tiposHistorial.stream()
.filter(x -> "specific value for CODIGO".equals(x.getCODIGO()))
.findFirst()
.orElse(null);
It will return TiposDeHistorial object in the list containing specific CODIGO value or null otherwise.
As for your question: "contains" method just returns "true" or "false", not an object. Moreover it uses "equals" method of your object, so it will not help if you want to search using fields.
Contains method will return true only if your object equals with ur list elements objects.
You can try extending equals method and have your own criteria which can work for either CODIGO or TIPO.
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
test other = (test) obj;
if (CODIGO == null) {
if (other.CODIGO != null)
return false;
} else if (!CODIGO.equals(other.CODIGO))
return false;
return true;
}
The answers already given here are all correct, just if You don't know java streams, and would like to check if the list contains both some CODIGO and TIPO fields, for me the simplest solution would be:
ArrayList<TiposDeHistorial> tiposHistorial = new ArrayList<>();
//add elements to the list
String tipo = "TIPO"; // the TIPO value You are looking for in the list
String codigo = "CODIGO"; // the CODIGO value You are looking for in the list
boolean containsTipo = false;
boolean containsCodigo = false;
for (TiposDeHistorial element: tiposHistorial) {
if (!containsTipo && element.getTIPO().equals(tipo)) {
containsTipo = true;
}
if (!containsCodigo && element.getCODIGO().equals(codigo) ){
containsCodigo = true;
}
if (containsTipo && containsCodigo)
break;
}
By editing it just a bit, You may also find which elements of the array contain the values You are looking for, if that will be Your intention
I have an issue with a TreeMap that we have defined a custom key object for. The issue is that after putting a few objects into the map, and trying to retrieve with the same key used to put on the map, I get a null. I believe this is caused by the fact that we have 2 data points on the key. One value is always populated and one value is not always populated. So it seems like the issue lies with the use of compareTo and equals. Unfortunately the business requirement for how our keys determine equality needs to be implemented this way.
I think this is best illustrated with code.
public class Key implements Comparable<Key> {
private String sometimesPopulated;
private String alwaysPopulated;
public int compareTo(Key aKey){
if(this.equals(aKey)){
return 0;
}
if(StringUtils.isNotBlank(sometimesPopulated) && StringUtils.isNotBlank(aKey.getSometimesPopulated())){
return sometimesPopulated.compareTo(aKey.getSometimesPopulated());
}
if(StringUtils.isNotBlank(alwaysPopulated) && StringUtils.isNotBlank(aKey.getAlwaysPopulated())){
return alwaysPopulated.compareTo(aKey.getAlwaysPopulated());
}
return 1;
}
public boolean equals(Object aObject){
if (this == aObject) {
return true;
}
final Key aKey = (Key) aObject;
if(StringUtils.isNotBlank(sometimesPopulated) && StringUtils.isNotBlank(aKey.getSometimesPopulated())){
return sometimesPopulated.equals(aKey.getSometimesPopulated());
}
if(StringUtils.isNotBlank(alwaysPopulated) && StringUtils.isNotBlank(aKey.getAlwaysPopulated())){
return alwaysPopulated.equals(aKey.getAlwaysPopulated());
}
return false;
}
So the issue occurs when trying to get a value off the map after putting some items on it.
Map<Key, String> map = new TreeMap<Key, String>();
Key aKey = new Key(null, "Hello");
map.put(aKey, "world");
//Put some more things on the map...
//they may have a value for sometimesPopulated or not
String value = map.get(aKey); // this = null
So why is the value null after just putting it in? I think the algorithm used by the TreeMap is sorting the map in an inconsistent manner because of the way I'm using compareTo and equals. I am open to suggestions on how to improve this code. Thanks
Your comparator violates the transitivity requirement.
Consider three objects:
Object A: sometimesPopulated="X" and alwaysPopulated="3".
Object B: sometimesPopulated="Y" and alwaysPopulated="1".
Object C: sometimesPopulated is blank and alwaysPopulated="2".
Using your comparator, A<B and B<C. Transitivity requires that A<C. However, using your comparator, A>C.
Since the comparator doesn't fulfil its contract, TreeMap is unable to do its job correctly.
I think the problem is that you are returning 1 from your compareTo if either of the sometimesPopulated values is blank or either of the alwaysPopulated values is blank. Remember that compareTo can be thought of returning the value of a subtraction operation and your's is not transitive. (a - b) can == (b - a) even when a != b.
I would return -1 if the aKey sometimesPopulated is not blank and the local sometimesPopulated is blank. If they are the same then I would do the same with alwaysPopulated.
I think your logic should be something like:
public int compareTo(Key aKey){
if(this.equals(aKey)){
return 0;
}
if (StringUtils.isBlank(sometimesPopulated)) {
if (StringUtils.isNotBlank(aKey.getSometimesPopulated())) {
return -1;
}
} else if (StringUtils.isBlank(aKey.getSometimesPopulated())) {
return 1;
} else {
int result = sometimesPopulated.compareTo(aKey.getSometimesPopulated());
if (result != 0) {
return result;
}
}
// same logic with alwaysPopulated
return 0;
}
I believe the problem is that you are treating two keys with both blank fields as greater than each other which could confuse the structure.
class Main {
public static void main(String... args) {
Map<Key, String> map = new TreeMap<Key, String>();
Key aKey = new Key(null, "Hello");
map.put(aKey, "world");
//Put some more things on the map...
//they may have a value for sometimesPopulated or not
String value = map.get(aKey); // this = "world"
System.out.println(value);
}
}
class Key implements Comparable<Key> {
private final String sometimesPopulated;
private final String alwaysPopulated;
Key(String alwaysPopulated, String sometimesPopulated) {
this.alwaysPopulated = defaultIfBlank(alwaysPopulated, "");
this.sometimesPopulated = defaultIfBlank(sometimesPopulated, "");
}
static String defaultIfBlank(String s, String defaultString) {
return s == null || s.trim().isEmpty() ? defaultString : s;
}
#Override
public int compareTo(Key o) {
int cmp = sometimesPopulated.compareTo(o.sometimesPopulated);
if (cmp == 0)
cmp = alwaysPopulated.compareTo(o.alwaysPopulated);
return cmp;
}
}
I think your equals, hashCode and compareTo methods should only use the field that is always populated. It's the only way to ensure the same object will always be found in the map regardless of if its optional field is set or not.
Second option, you could write an utility method that tries to find the value in the map, and if no value is found, tries again with the same key but with (or without) the optional field set.