I want to store values that are binded to a name + number.
Like, (John,1) (RED) and (John,2) (BLUE) and (Elize,1) (GREEN)
So how can i store a 2 keys that are combineded unique?
Create a new type which represents the composite key (the name and the number here). You'll need to override hashCode() and equals(), and I'd strongly advise you to make the type immutable. For example:
public final class NameIntPair {
private final int intValue;
private final String name;
public NameIntPair(int intValue, String name) {
this.intValue = intValue;
this.name = name;
}
#Override
public int hashCode() {
int hash = 17;
hash = hash * 31 + intValue;
hash = hash * 31 + (name == null ? 0 : name.hashCode());
return hash;
}
#Override
public boolean equals(Object obj) {
if (!(obj instanceof NameIntPair)) {
return false;
}
if (this == obj) {
return true;
}
NameIntPair other = (NameIntPair) obj;
return other.intValue == intValue && Objects.equal(other.name, name);
}
}
I'm using Objects.equal from Guava for convenience to avoid explicit nullity checks here - if you're not using Guava, you'd either have to use an equivalent or handle nullity in the code. Alternatively, you may well want to prevent null names, validating this in the constructor.
I'll use a String concatenation if I'm sure about the uniqueness of the combination and if the keys object are easy to stringify. I might use a special character to join the keys (like "John#1" and "John#2").
If I'm not sure about that I'll use Guava's Table:
Typically, when you are trying to index on more than one key at a
time, you will wind up with something like Map(FirstName,
Map(LastName, Person)), which is ugly and awkward to use. Guava
provides a new collection type, Table, which supports this use case
for any "row" type and "column" type
So a Table is
A collection that associates an ordered pair of keys, called a row key
and a column key, with a single value.
Define your specific Key class like this :
public class Key {
final String name;
final int number;
public Key(String name, int number) {
this.name = name;
this.number = number;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getOuterType().hashCode();
result = prime * result
+ ((name == null) ? 0 : name.hashCode());
result = prime * result + number;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Key other = (Key) obj;
if (!getOuterType().equals(other.getOuterType()))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (number != other.number)
return false;
return true;
}
private Test getOuterType() {
return Test.this;
}
}
The important point is to ensure that you respect the contract of equals and hashCode to enable your collection (any standard collection using key) to work as intended.
Here I simply used the generated methods produced by Eclipse but there are also many dynamic utilities (for example in Guava) helping you on this topic.
There is also simple alternative approach with concatenated key suitable for this case:
public static String getKey(String name, int number) {
return name + number;
}
If names are not too long, overhead for string concatenation won't be greater than for creating composite key objects suggested in other answers.
Related
I'm working with HashMap since few days, and facing below weird situation.
Case 1:Changed Key which is already existing in HashMap, and print HashMap
Case 2: Changed key which is already existing in HashMap and Put that key again into the HashMap. Print HashMap.
Please find below code as well as two different output of two case.
Could you please anyone let me know, whats going on in below code.
import java.util.HashMap;
import java.util.Set;
class Emp{
private String id ;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Emp(String id) {
super();
this.id = id;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.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;
Emp other = (Emp) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
#Override
public String toString() {
return "Emp [id=" + id + "]";
}
}
public class HashMapChanges {
public static void main(String[] args) {
// TODO Auto-generated method stub
Emp e1 = new Emp("1");
Emp e2 = new Emp("2");
Emp e3 = new Emp("3");
Emp e4 = new Emp("4");
HashMap<Emp, String> hm = new HashMap<Emp,String>();
hm.put(e1,"One");
hm.put(e2,"Two");
hm.put(e3,"Three");
hm.put(e4,"Four");
e1.setId("5");
/** Uncomment below line to get different output**/
//hm.put(e1,"Five-5");
Set<Emp> setEmpValue = hm.keySet();
for(Emp e : setEmpValue){
System.out.println("Key"+ e +" Value "+ hm.get(e));
}
}
}
Output of above code :
KeyEmp [id=2] Value Two
KeyEmp [id=5] Value null
KeyEmp [id=4] Value Four
KeyEmp [id=3] Value Three
Output After uncommenting line
KeyEmp [id=5] Value Five-5
KeyEmp [id=2] Value Two
KeyEmp [id=5] Value Five-5
KeyEmp [id=4] Value Four
KeyEmp [id=3] Value Three
Using mutable objects as keys in a Map is not permitted when the key used to determine its location in the Map is mutable.
From the Javadoc for java.util.Map<K,V>:
Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map.
You are violating the contract required of map keys because your Emp object is mutable, and the change modifies the attribute used to determine where in the map the key resides.
The result is undefined behavior.
I suspect you have misunderstood the Map concept, based on your code, but without understanding what you're actually trying to achieve we really cannot help further. I suggest you ask a new question explaining your actual goals.
You overwrite the hashCode() and equals() method ,
then ,the Map's key is the hashCode result.
then id=1 and id=5 are two different items.
You can comment the two methods and try again.
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 was asked this in interview. using Google Guava or MultiMap is not an option.
I have a class
public class Alpha
{
String company;
int local;
String title;
}
I have many instances of this class (in order of millions). I need to process them and at the end find the unique ones and their duplicates.
e.g.
instance --> instance1, instance5, instance7 (instance1 has instance5 and instance7 as duplicates)
instance2 --> instance2 (no duplicates for instance 2)
My code works fine
declare datastructure
HashMap<Alpha,ArrayList<Alpha>> hashmap = new HashMap<Alpha,ArrayList<Alpha>>();
Add instances
for (Alpha x : arr)
{
ArrayList<Alpha> list = hashmap.get(x); ///<<<<---- doubt about this. comment#1
if (list == null)
{
list = new ArrayList<Alpha>();
hashmap.put(x, list);
}
list.add(x);
}
Print instances and their duplicates.
for (Alpha x : hashmap.keySet())
{
ArrayList<Alpha> list = hashmap.get(x); //<<< doubt about this. comment#2
System.out.println(x + "<---->");
for(Alpha y : list)
{
System.out.print(y);
}
System.out.println();
}
Question: My code works, but why? when I do hashmap.get(x); (comment#1 in code). it is possible that two different instances might have same hashcode. In that case, I will add 2 different objects to the same List.
When I retrieve, I should get a List which has 2 different instances. (comment#2) and when I iterate over the list, I should see at least one instance which is not duplicate of the key but still exists in the list. I don't. Why?. I tried returning constant value from my hashCode function, it works fine.
If you want to see my implementation of equals and hashCode,let me know.
Bonus question: Any way to optimize it?
Edit:
#Override
public boolean equals(Object obj) {
if (obj==null || obj.getClass()!=this.getClass())
return false;
if (obj==this)
return true;
Alpha guest = (Alpha)obj;
return guest.getLocal()==this.getLocal()
&& guest.getCompany() == this.getCompany()
&& guest.getTitle() == this.getTitle();
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (title==null?0:title.hashCode());
result = prime * result + local;
result = prime * result + (company==null?0:company.hashCode());
return result;
}
it is possible that two different instances might have same hashcode
Yes, but hashCode method is used to identify the index to store the element. Two or more keys could have the same hashCode but that's why they are also evaluated using equals.
From Map#containsKey javadoc:
Returns true if this map contains a mapping for the specified key. More formally, returns true if and only if this map contains a mapping for a key k such that (key==null ? k==null : key.equals(k)). (There can be at most one such mapping.)
Some enhancements to your current code:
Code oriented to interfaces. Use Map and instantiate it by HashMap. Similar to List and ArrayList.
Compare Strings and Objects in general using equals method. == compares references, equals compares the data stored in the Object depending the implementation of this method. So, change the code in Alpha#equals:
public boolean equals(Object obj) {
if (obj==null || obj.getClass()!=this.getClass())
return false;
if (obj==this)
return true;
Alpha guest = (Alpha)obj;
return guest.getLocal().equals(this.getLocal())
&& guest.getCompany().equals(this.getCompany())
&& guest.getTitle().equals(this.getTitle());
}
When navigating through all the elements of a map in pairs, use Map#entrySet instead, you can save the time used by Map#get (since it is supposed to be O(1) you won't save that much but it is better):
for (Map.Entry<Alpha, List<Alpha>> entry : hashmap.keySet()) {
List<Alpha> list = entry.getValuee();
System.out.println(entry.getKey() + "<---->");
for(Alpha y : list) {
System.out.print(y);
}
System.out.println();
}
Use equals along with hashCode to solve the collision state.
Steps:
First compare on the basis of title in hashCode()
If the title is same then look into equals() based on company name to resolve the collision state.
Sample code
class Alpha {
String company;
int local;
String title;
public Alpha(String company, int local, String title) {
this.company = company;
this.local = local;
this.title = title;
}
#Override
public int hashCode() {
return title.hashCode();
}
#Override
public boolean equals(Object obj) {
if (obj instanceof Alpha) {
return this.company.equals(((Alpha) obj).company);
}
return false;
}
}
...
Map<Alpha, ArrayList<Alpha>> hashmap = new HashMap<Alpha, ArrayList<Alpha>>();
hashmap.put(new Alpha("a", 1, "t1"), new ArrayList<Alpha>());
hashmap.put(new Alpha("b", 2, "t1"), new ArrayList<Alpha>());
hashmap.put(new Alpha("a", 3, "t1"), new ArrayList<Alpha>());
System.out.println("Size : "+hashmap.size());
Output
Size : 2
This question already has answers here:
Why do I need to override the equals and hashCode methods in Java?
(31 answers)
Closed 9 years ago.
I have a hashmap which key is an object of my inner class "Key".
My problem is that when I use get(key) it never gives anything back. Since get works with equals I have overwritten equals in my Key class, so it should work for the get method, but apparently it does not.
Any suggestions?
CODE:
public class Infrastruktur
{
private Zuechter online;
private HashMap<Key,Zuechter> zuechter;
Infrastruktur()
{
zuechter = new HashMap<Key,Zuechter>();
}
}
public void login(String name, String passwort)
{
Key hashMapKey = new Key(name, passwort);
if(this.zuechter.get(hashMapKey) != null)
this.online = this.zuechter.get(hashMapKey);
}
public void register(String name, String passwort)
{
if(name != null && passwort != null)
{
this.zuechter.put(new Key(name,passwort),new Zuechter());
login(name, passwort);
}
}
public void logOut()
{
this.online = null;
}
public Zuechter getOnline() {
return this.online;
}
private class Key
{
String name;
String passwort;
Key(String name, String passwort)
{
this.name = name;
this.passwort = passwort;
}
#Override
public boolean equals(Object o)
{
if (o == null) return false;
if (o == this) return true;
if (!(o instanceof Key)) return false;
Key key = (Key)o;
if(this.name.equals(key.name) && this.passwort.equals(key.passwort)) return true;
return false;
}
}
/* Testing */
public static void main(String[] args)
{
Infrastruktur inf = new Infrastruktur();
inf.register("Jakob", "passwort");
inf.logOut();
inf.login("Jakob", "passwort");
System.out.println(inf.getOnline().test());
}
}
If I run the class this is the output I get:
not found
not found
Exception in thread "main" java.lang.NullPointerException
at Infrastruktur.main(Infrastruktur.java:105)
You should also implement hashCode() for your Key class. An example implementation could be:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + name.hashCode();
result = prime * result + passwort.hashCode();
return result;
}
Use Eclipse to generate the hashCode method of your class. In any Map scenario, Java hashes the key value to allow 0(1) read access.
It simply hashes to jump to a reference if found. All Java IDEs have a Generate hashCode and equals option. A simple example, with null checks omitted.
#Override
public int hashCode() {
int hash = 3;
hash = 7 * hash + this.name.hashCode();
hash = 7 * hash + this.passwort.hashCode();
return hash;
}
You must override hashCode() in every class that overrides equals(). Failure to do so will result in a violation of the general contract for Object.hashCode(), which will prevent your class from functioning properly in conjunction with all hash-based collections, including HashMap, HashSet, and Hashtable.
from Effective Java, by Joshua Bloch
tl;dr either generate hashCode() manually,
#Override
public int hashCode() {
int hash = 31;
hash = 29 * hash + Objects.hashCode(name);
hash = 29 * hash + Objects.hashCode(passwort);
return hash;
}
use IDE hashCode generation, or just use generic (albeit slower)
#Override
public int hashCode() {
return Objects.hash( name, passwort );
}
... you can even write a generic hashCode() for any class using reflection (very slow, but good as placeholder)
btw, omitting null checks in hashCode() for mutable or immutable objects with null as a valid field value is one of the easiest ways to introduce bugs into code - that's exactly why either explicit check or Objects.hashCode() is needed.
I want to compare database dump to xml and *.sql. In debagge toRemove and toAdd only differ in dimension. toRemove has size 3, toAdd has size 4. But after running the code, removeAll, toRemove has size 3 and toAdd has size 4. What's wrong?
final DBHashSet fromdb = new DBHashSet(strURL, strUser, strPassword);
final DBHashSet fromxml = new DBHashSet(namefile);
Set<DBRecord> toRemove = new HashSet<DBRecord>(fromdb);
toRemove.removeAll(fromxml);
Set<DBRecord> toAdd = new HashSet<DBRecord>(fromxml);
toAdd.removeAll(fromdb);
Update:
public class DBRecord {
public String depcode;
public String depjob;
public String description;
public DBRecord(String newdepcode, String newdepjobe, String newdesc) {
this.depcode = newdepcode;
this.depjob = newdepjobe;
this.description = newdesc;
}
public String getKey() {
return depcode + depjob;
}
public boolean IsEqualsKey(DBRecord rec) {
return (this.getKey().equals(rec.getKey()));
}
public boolean equals(Object o) {
if (o == this)
return true;
if (o == null)
return false;
if (!(getClass() == o.getClass()))
return false;
else {
DBRecord rec = (DBRecord) o;
if ((rec.depcode.equals(this.depcode)) && (rec.depjob.equals(this.depjob)))
return true;
else
return false;
}
}
}
In order to properly use HashSet (and HashMap, for that matter), you must implement a hashCode() as per the following contract:
Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
The code you've supplied for DBRecord does not overide it, hence the problem.
You'd probably want to override it in the following way, or something similar:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + depcode.hashCode();
result = prime * result + depjob.hashCode());
return result;
}