why java class WeakReference does not override hashcode and equals - java

I was expecting the WeakReference class to override hashCode and equals methods like this
class WeakReference<T>{
T ref;
int hashCode(){
return ref.hashCode();
}
boolean equals(Object o){
return ref.equals(o);
}
}
So that I could use We WeakReference directly as key in hashmaps like
Person p1 = new Person("p1");
WeakReference<Person> wr = new WeakReference<Person>(p1);
map.put(wr, "some value object");
But when I tested I found out that hashCode and equals are not overridden
Person p1 = new Person("p1");
WeakReference<Person> wr = new WeakReference<Person>(p1);
WeakReference<Person> wr2 = new WeakReference<Person>(p1);
System.out.println(wr.hashCode()); // prints x
System.out.println(wr2.hashCode()); // prints y
System.out.println(wr.equals(wr2)); // prints false
Any specific reasons reasons that hashCode and equals are not overridden in WeakReference class?

An important aspect of any key on a Map (or element of a Set) is that it must be immutable (or at least not change) once it has been added to the collection. Changing a key has undefined behaviour which is highly unlikely to work.
A WeakReference can change at any time due to a GC being performed i.e. in ways you have no control over, which makes the equals/hashCode inappropriate for general collections which use these.
I was trying to make MyWeakConcurrentHashMap
A simple way of doing this is to have an array of WeakHashMaps. e.g. 32 partitions . Use the hashCode() to determine which WeakHashMap to use. This way you can have a thread accessing each of the individual WeakHashMap at once (best case)
As you have more concurrency you can increase the number of partitions.

It seems like WeakHashMap uses Entry which extends WeakReference
and overrides both hashCode and equals, you can take a look of their implementation.
/**
* The entries in this hash table extend WeakReference, using its main ref
* field as the key.
*/
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value;
final int hash;
Entry<K,V> next;
/**
* Creates new entry.
*/
Entry(Object key, V value,
ReferenceQueue<Object> queue,
int hash, Entry<K,V> next) {
super(key, queue);
this.value = value;
this.hash = hash;
this.next = next;
}
#SuppressWarnings("unchecked")
public K getKey() {
return (K) WeakHashMap.unmaskNull(get());
}
public V getValue() {
return value;
}
public V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
K k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
V v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public int hashCode() {
K k = getKey();
V v = getValue();
return Objects.hashCode(k) ^ Objects.hashCode(v);
}
public String toString() {
return getKey() + "=" + getValue();
}
}

Related

HashMap.containsKey(key) failing to find key, with custom class used as key type

TL;DR: I made a hashmap object with my own "Pair" class objects to be used as Keys. When I use hashmap.containsKey(Pair), it fails to find the key.
I have a class called Pair, code shown below. It's supposed to be a container for two objects. The first object can be of any type, whereas the second object must be an integer. This isn't great design but I coded it this way so I could reuse the class for other purposes within my program.
import java.util.ArrayList;
public class Pair<L> {
private L left;
private int right;
public Pair(L left, int right) {
this.left = left;
this.right = right;
}
public L getLeft() { return left; }
public int getRight() { return right; }
public void ToString() {
System.out.println(left + "," + right);
}
public boolean equals(Pair p) {
return (this.getLeft().equals(p.getLeft()) && this.getRight() == p.getRight());
}
public ArrayList<Pair> neighbors(int rowLimit, int ColumnLimit) {
ArrayList<Pair> neighbors = new ArrayList<Pair>();
Pair neighborL;
Pair neighborR;
Pair neighborU;
Pair neighborD;
if (((int)this.left-1 >= 0)) {
neighborU = new Pair((int)this.left-1, this.right);
// neighborU.ToString();
neighbors.add(neighborU);
}
if ((int)this.left+1 < rowLimit) {
neighborD = new Pair((int)this.left+1, this.right);
// neighborD.ToString();
neighbors.add(neighborD);
}
if ((int)this.right-1 >= 0) {
neighborL = new Pair((int)this.left, this.right-1);
// neighborL.ToString();
neighbors.add(neighborL);
}
if ((int)this.right+1 < ColumnLimit) {
neighborR = new Pair((int)this.left, this.right+1);
// neighborR.ToString();
neighbors.add(neighborR);
}
return neighbors;
}
}
I'm storing Pairs as keys in a hashmap like this:
Map<Pair, Integer> costSoFar = new HashMap<Pair, Integer>();
costSoFar.put(sLocale, 0);
When I run the line below, which is to say, if the key is not in the hashmap:
if (!costSoFar.containsKey(next))
It evaluates to true, even when I know the key is in there, as I've checked through debugging.
If anyone can help clear up why the hashmap isn't recognizing the keys it'd be much appreciated. Perhaps my equals method isn't up to scratch?
Why are you using Generics if L seems to be int too?
Replace your equals:
public boolean equals(Object o) {
if (o instanceof Pair){
Pair p = (Pair)o;
return (this.getLeft().equals(p.getLeft()) && this.getRight() == p.getRight());
}
return false;
}
And implement int hashCode():
public int hashCode() {
return this.getLeft() * 31 + this.getRight();
}
Method containsKey(Object) in java.util.HashMap use the java.util.HashMap.getEntry(Object) which call hashCode() to obtain the hashCode for the Key and use it to retrive the object. You need to override java.lang.Object.hashCode() to make your code properly work.

Implementation of Custom HashMap code issues

I am preparing my own custom HashMap implementation in Java. Below is my imlementation.
public class Entry<K,V> {
private final K key;
private V value;
private Entry<K,V> next;
public Entry(K key, V value, Entry<K,V> next) {
this.key = key;
this.value = value;
this.next = next;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
public Entry<K, V> getNext() {
return next;
}
public void setNext(Entry<K, V> next) {
this.next = next;
}
public K getKey() {
return key;
}
}
public class MyCustomHashMap<K,V> {
private int DEFAULT_BUCKET_COUNT = 10;
private Entry<K,V>[] buckets;
public MyCustomHashMap() {
buckets = new Entry[DEFAULT_BUCKET_COUNT];
for (int i = 0;i<DEFAULT_BUCKET_COUNT;i++)
buckets[i] = null;
}
public void put(K key,V value){
/**
* This is the new node.
*/
Entry<K,V> newEntry = new Entry<K,V>(key, value, null);
/**
* If key is null, then null keys always map to hash 0, thus index 0
*/
if(key == null){
buckets[0] = newEntry;
}
/**
* get the hashCode of the key.
*/
int hash = hash(key);
/**
* if the index does of the bucket does not contain any element then assign the node to the index.
*/
if(buckets[hash] == null) {
buckets[hash] = newEntry;
} else {
/**
* we need to traverse the list and compare the key with each of the keys till the keys match OR if the keys does not match then we need
* to add the node at the end of the linked list.
*/
Entry<K,V> previous = null;
Entry<K,V> current = buckets[hash];
while(current != null) {
boolean done = false;
while(!done) {
if(current.getKey().equals(key)) {
current.setValue(value);
done = true; // if the keys are same then replace the old value with the new value;
} else if (current.getNext() == null) {
current.setNext(newEntry);
done = true;
}
current = current.getNext();
previous = current;
}
}
previous.setNext(newEntry);
}
}
public V getKey(K key) {
int hash = hash(key);
if(buckets[hash] == null) {
return null;
} else {
Entry<K,V> temp = buckets[hash];
while(temp != null) {
if(temp.getKey().equals(key))
return temp.getValue(); // returns value corresponding to key.
temp = temp.getNext();
}
return null; //return null if key is not found.
}
}
public void display() {
for(int i = 0; i < DEFAULT_BUCKET_COUNT; i++) {
if(buckets[i] != null) {
Entry<K,V> entry = buckets[i];
while(entry != null){
System.out.print("{"+entry.getKey()+"="+entry.getValue()+"}" +" ");
entry=entry.getNext();
}
}
}
}
public int bucketIndexForKey(K key) {
int bucketIndex = key.hashCode() % buckets.length;
return bucketIndex;
}
/**
*
* #param key
* #return
*/
private int hash(K key){
return Math.abs(key.hashCode()) % buckets.length;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
MyCustomHashMap<String, Integer> myCustomHashMap = new MyCustomHashMap<String, Integer>();
myCustomHashMap.put("S", 22);
myCustomHashMap.put("S", 1979);
myCustomHashMap.put("V", 5);
myCustomHashMap.put("R", 31);
System.out.println("Value corresponding to key R: "+myCustomHashMap.getKey("R"));
System.out.println("Value corresponding to key V: "+myCustomHashMap.getKey("V"));
System.out.println("Displaying the contents of the HashMap:: ");
myCustomHashMap.display();
}
}
1) I feel that put (K key,V value) is somewhat flawed. Please do kindly validate and let me know what's wrong here. On entering the same key its giving me wrong result. I have not yet tested it for collision cases having different keys.
2) It is said that we rehash the hashCode so that it eliminates wrong implementation of hashCode. how do I do it because if I give hashCode of key i.e. hash(key.hashCode()) then it dosn't take as it can't compute hashCode of int. How to do this?
Any help would be highly appreciated.
Thanks
Sid
You handle null key incorrectly :
if(key == null){
buckets[0] = newEntry;
}
It's possible that buckets[0] already contains entries, in which case you will lose those entries.
The following loop has some issues :
Entry<K,V> previous = null;
Entry<K,V> current = buckets[hash];
while(current != null) {
boolean done = false;
while(!done) {
if(current.getKey().equals(key)) {
current.setValue(value);
done = true;
} else if (current.getNext() == null) {
current.setNext(newEntry);
done = true;
}
current = current.getNext();
previous = current; // you are not really setting previous to
// to the previous Entry in the list - you
// are setting it to the current Entry
}
}
previous.setNext(newEntry); // you don't need this statement. You
// already have a statement inside the
// loop that adds the new Entry to the list
It looks like removing any statements related to previous will fix this loop.
EDIT:
As kolakao commented, in order for your implementation to be efficient (i.e. require expected constant time for get and put), you must resize the HashMap when the number of entries exceeds some threshold (in order for the average number of entries in each bucket to be bound by a constant).
It is said that we rehash the hashCode so that it eliminates wrong implementation of hashCode. how do I do it because if I give hashCode of key i.e. hash(key.hashCode()) then it dosn't take as it can't compute hashCode of int. How to do this?
The idea of re-hashing doesn't involve calling hashCode for the hashCode of the key. It involves running some hardcoded function on the value obtained by key.hashCode().
For example, in Java 7 implementation of HashMap, the following function is used :
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
Then you use it with :
int hash = hash(key.hashCode());
int bucket = hash % buckets.length;

Key of a HashMap is a pair of integers. How to convert and store it? [duplicate]

I have a 2D array of Integers. I want them to be put into a HashMap. But I want to access the elements from the HashMap based on Array Index. Something like:
For A[2][5], map.get(2,5) which returns a value associated with that key. But how do I create a hashMap with a pair of keys? Or in general, multiple keys: Map<((key1, key2,..,keyN), Value) in a way that I can access the element with using get(key1,key2,...keyN).
EDIT : 3 years after posting the question, I want to add a bit more to it
I came across another way for NxN matrix.
Array indices, i and j can be represented as a single key the following way:
int key = i * N + j;
//map.put(key, a[i][j]); // queue.add(key);
And the indices can be retrevied from the key in this way:
int i = key / N;
int j = key % N;
There are several options:
2 dimensions
Map of maps
Map<Integer, Map<Integer, V>> map = //...
//...
map.get(2).get(5);
Wrapper key object
public class Key {
private final int x;
private final int y;
public Key(int x, int y) {
this.x = x;
this.y = y;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Key)) return false;
Key key = (Key) o;
return x == key.x && y == key.y;
}
#Override
public int hashCode() {
int result = x;
result = 31 * result + y;
return result;
}
}
Implementing equals() and hashCode() is crucial here. Then you simply use:
Map<Key, V> map = //...
and:
map.get(new Key(2, 5));
Table from Guava
Table<Integer, Integer, V> table = HashBasedTable.create();
//...
table.get(2, 5);
Table uses map of maps underneath.
N dimensions
Notice that special Key class is the only approach that scales to n-dimensions. You might also consider:
Map<List<Integer>, V> map = //...
but that's terrible from performance perspective, as well as readability and correctness (no easy way to enforce list size).
Maybe take a look at Scala where you have tuples and case classes (replacing whole Key class with one-liner).
When you create your own key pair object, you should face a few thing.
First, you should be aware of implementing hashCode() and equals(). You will need to do this.
Second, when implementing hashCode(), make sure you understand how it works. The given user example
public int hashCode() {
return this.x ^ this.y;
}
is actually one of the worst implementations you can do. The reason is simple: you have a lot of equal hashes! And the hashCode() should return int values that tend to be rare, unique at it's best. Use something like this:
public int hashCode() {
return (X << 16) + Y;
}
This is fast and returns unique hashes for keys between -2^16 and 2^16-1 (-65536 to 65535). This fits in almost any case. Very rarely you are out of this bounds.
Third, when implementing equals() also know what it is used for and be aware of how you create your keys, since they are objects. Often you do unnecessary if statements cause you will always have the same result.
If you create keys like this: map.put(new Key(x,y),V); you will never compare the references of your keys. Cause everytime you want to acces the map, you will do something like map.get(new Key(x,y));. Therefore your equals() does not need a statement like if (this == obj). It will never occur.
Instead of if (getClass() != obj.getClass()) in your equals() better use if (!(obj instanceof this)). It will be valid even for subclasses.
So the only thing you need to compare is actually X and Y. So the best equals() implementation in this case would be:
public boolean equals (final Object O) {
if (!(O instanceof Key)) return false;
if (((Key) O).X != X) return false;
if (((Key) O).Y != Y) return false;
return true;
}
So in the end your key class is like this:
public class Key {
public final int X;
public final int Y;
public Key(final int X, final int Y) {
this.X = X;
this.Y = Y;
}
public boolean equals (final Object O) {
if (!(O instanceof Key)) return false;
if (((Key) O).X != X) return false;
if (((Key) O).Y != Y) return false;
return true;
}
public int hashCode() {
return (X << 16) + Y;
}
}
You can give your dimension indices X and Y a public access level, due to the fact they are final and do not contain sensitive information. I'm not a 100% sure whether private access level works correctly in any case when casting the Object to a Key.
If you wonder about the finals, I declare anything as final which value is set on instancing and never changes - and therefore is an object constant.
You can't have an hash map with multiple keys, but you can have an object that takes multiple parameters as the key.
Create an object called Index that takes an x and y value.
public class Index {
private int x;
private int y;
public Index(int x, int y) {
this.x = x;
this.y = y;
}
#Override
public int hashCode() {
return this.x ^ this.y;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Index other = (Index) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
}
Then have your HashMap<Index, Value> to get your result. :)
Implemented in common-collections MultiKeyMap
Java 7+ contains a new Map.Entry<K,V> class that you can use as key for your map (or entry for your set). Java 9+ also contains a Map.entry(K k, V v) method to easily create new Map.Entry objects.
Usage:
Map<Map.Entry<Integer,Integer>, Integer> map = new HashMap<>();
map.put(Map.entry(1, 2), 0);
There is also Pair<K, V> in javafx.util
Map<Pair<Integer,Integer>, Integer> map = new HashMap<>();
map.put(new Pair(1, 2), 0);
Two possibilities. Either use a combined key:
class MyKey {
int firstIndex;
int secondIndex;
// important: override hashCode() and equals()
}
Or a Map of Map:
Map<Integer, Map<Integer, Integer>> myMap;
Use a Pair as keys for the HashMap. JDK has no Pair, but you can either use a 3rd party libraray such as http://commons.apache.org/lang or write a Pair taype of your own.
Create a value class that will represent your compound key, such as:
class Index2D {
int first, second;
// overrides equals and hashCode properly here
}
taking care to override equals() and hashCode() correctly. If that seems like a lot of work, you might consider some ready made generic containers, such as Pair provided by apache commons among others.
There are also many similar questions here, with other ideas, such as using Guava's Table, although allows the keys to have different types, which might be overkill (in memory use and complexity) in your case since I understand your keys are both integers.
If they are two integers you can try a quick and dirty trick: Map<String, ?> using the key as i+"#"+j.
If the key i+"#"+j is the same as j+"#"+i try min(i,j)+"#"+max(i,j).
You can also use guava Table implementation for this.
Table represents a special map where two keys can be specified in combined fashion to refer to a single value. It is similar to creating a map of maps.
//create a table
Table<String, String, String> employeeTable = HashBasedTable.create();
//initialize the table with employee details
employeeTable.put("IBM", "101","Mahesh");
employeeTable.put("IBM", "102","Ramesh");
employeeTable.put("IBM", "103","Suresh");
employeeTable.put("Microsoft", "111","Sohan");
employeeTable.put("Microsoft", "112","Mohan");
employeeTable.put("Microsoft", "113","Rohan");
employeeTable.put("TCS", "121","Ram");
employeeTable.put("TCS", "122","Shyam");
employeeTable.put("TCS", "123","Sunil");
//get Map corresponding to IBM
Map<String,String> ibmEmployees = employeeTable.row("IBM");
You could create your key object something like this:
public class MapKey {
public Object key1;
public Object key2;
public Object getKey1() {
return key1;
}
public void setKey1(Object key1) {
this.key1 = key1;
}
public Object getKey2() {
return key2;
}
public void setKey2(Object key2) {
this.key2 = key2;
}
public boolean equals(Object keyObject){
if(keyObject==null)
return false;
if (keyObject.getClass()!= MapKey.class)
return false;
MapKey key = (MapKey)keyObject;
if(key.key1!=null && this.key1==null)
return false;
if(key.key2 !=null && this.key2==null)
return false;
if(this.key1==null && key.key1 !=null)
return false;
if(this.key2==null && key.key2 !=null)
return false;
if(this.key1==null && key.key1==null && this.key2 !=null && key.key2 !=null)
return this.key2.equals(key.key2);
if(this.key2==null && key.key2==null && this.key1 !=null && key.key1 !=null)
return this.key1.equals(key.key1);
return (this.key1.equals(key.key1) && this.key2.equals(key2));
}
public int hashCode(){
int key1HashCode=key1.hashCode();
int key2HashCode=key2.hashCode();
return key1HashCode >> 3 + key2HashCode << 5;
}
}
The advantage of this is: It will always make sure you are covering all the scenario's of Equals as well.
NOTE: Your key1 and key2 should be immutable. Only then will you be able to construct a stable key Object.
we can create a class to pass more than one key or value and the object of this class can be used as a parameter in map.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;
public class key1 {
String b;
String a;
key1(String a,String b)
{
this.a=a;
this.b=b;
}
}
public class read2 {
private static final String FILENAME = "E:/studies/JAVA/ReadFile_Project/nn.txt";
public static void main(String[] args) {
BufferedReader br = null;
FileReader fr = null;
Map<key1,String> map=new HashMap<key1,String>();
try {
fr = new FileReader(FILENAME);
br = new BufferedReader(fr);
String sCurrentLine;
br = new BufferedReader(new FileReader(FILENAME));
while ((sCurrentLine = br.readLine()) != null) {
String[] s1 = sCurrentLine.split(",");
key1 k1 = new key1(s1[0],s1[2]);
map.put(k1,s1[2]);
}
for(Map.Entry<key1,String> m:map.entrySet()){
key1 key = m.getKey();
String s3 = m.getValue();
System.out.println(key.a+","+key.b+" : "+s3);
}
// }
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (br != null)
br.close();
if (fr != null)
fr.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
You can download it from the below link:
https://github.com/VVS279/DoubleKeyHashMap/blob/master/src/com/virtualMark/doubleKeyHashMap/DoubleKeyHashMap.java
https://github.com/VVS279/DoubleKeyHashMap
You can use double key: value hashmap,
DoubleKeyHashMap<Integer, Integer, String> doubleKeyHashMap1 = new
DoubleKeyHashMap<Integer, Integer, String>();
DoubleKeyHashMap<String, String, String> doubleKeyHashMap2 = new
DoubleKeyHashMap<String, String, String>();
Using org.apache.commons.lang3.tuple.Pair is very easy;
Map<String, Pair<String, Integer>> map= new HashMap<>();
map.put("key", Pair.of("a", 1));
int value = map.get("key").getRight();

How to Store a One to One Relationship in Java

I am wondering if there is a data structure that can store one to one relationship in Java.
For example, for mapping the month of the year with its number and vice versa.
I know I can use two HashMaps, but I want to know if there is another data structure that also checks the value when I put a new pair.
There is none in standard Java though. You can however use Guava's BiMap if you can use a 3rd party library.
A bimap (or "bidirectional map") is a map that preserves the
uniqueness of its values as well as that of its keys. This constraint
enables bimaps to support an "inverse view", which is another bimap
containing the same entries as this bimap but with reversed keys and
values.
There is no data structure for that, but I think you can use a parameterized Pair for this task. This class is so popular that you will it on the internet.
Here is one on SO:
Generic pair class
Needed a solution to this recently, but didn't want to rely on a plugin....so I extended the base Java Class. This implementation does not allow null values.
public class OneToOneMap<K,V> extends HashMap<K,V> {
public OneToOneMap() {
super();
}
public OneToOneMap(int initialCapacity) {
super(initialCapacity);
}
public OneToOneMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
}
public OneToOneMap(Map<? extends K, ? extends V> m) {
super(m);
dedup();
}
#Override
public void putAll(Map<? extends K, ? extends V> m){
super.putAll(m);
dedup();
}
#Override
public V put(K key, V value) {
if( key == null || value == null ) return null;
removeValue(value);
return super.put(key,value);
}
public K getKey( V value ){
if( value == null || this.size() == 0 ) return null;
Set<K> keys = new HashSet<>();
keys.addAll(keySet());
for( K key : keys ){
if( value.equals(get(key) )) return key;
}
return null;
}
public boolean hasValue( V value ){
return getKey(value) != null;
}
public boolean hasKey( K key ){
return get(key) != null;
}
public void removeValue( V remove ){
V value;
Set<K> keys = new HashSet<>();
keys.addAll(keySet());
for( K key : keys ){
value = get(key);
if( value == null || key == null || value.equals(remove)) remove(key);
}
}
//can be used when a new map is assigned to clean it up
public void dedup(){
V value;
Set<V> values = new HashSet<>();
Set<K> keys = new HashSet<>();
keys.addAll(keySet());
for( K key : keys ){
value = get(key);
if( value == null || key == null || values.contains(value) ) remove(key);
else values.add(value);
}
}
}

How to create a HashMap with two keys (Key-Pair, Value)?

I have a 2D array of Integers. I want them to be put into a HashMap. But I want to access the elements from the HashMap based on Array Index. Something like:
For A[2][5], map.get(2,5) which returns a value associated with that key. But how do I create a hashMap with a pair of keys? Or in general, multiple keys: Map<((key1, key2,..,keyN), Value) in a way that I can access the element with using get(key1,key2,...keyN).
EDIT : 3 years after posting the question, I want to add a bit more to it
I came across another way for NxN matrix.
Array indices, i and j can be represented as a single key the following way:
int key = i * N + j;
//map.put(key, a[i][j]); // queue.add(key);
And the indices can be retrevied from the key in this way:
int i = key / N;
int j = key % N;
There are several options:
2 dimensions
Map of maps
Map<Integer, Map<Integer, V>> map = //...
//...
map.get(2).get(5);
Wrapper key object
public class Key {
private final int x;
private final int y;
public Key(int x, int y) {
this.x = x;
this.y = y;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Key)) return false;
Key key = (Key) o;
return x == key.x && y == key.y;
}
#Override
public int hashCode() {
int result = x;
result = 31 * result + y;
return result;
}
}
Implementing equals() and hashCode() is crucial here. Then you simply use:
Map<Key, V> map = //...
and:
map.get(new Key(2, 5));
Table from Guava
Table<Integer, Integer, V> table = HashBasedTable.create();
//...
table.get(2, 5);
Table uses map of maps underneath.
N dimensions
Notice that special Key class is the only approach that scales to n-dimensions. You might also consider:
Map<List<Integer>, V> map = //...
but that's terrible from performance perspective, as well as readability and correctness (no easy way to enforce list size).
Maybe take a look at Scala where you have tuples and case classes (replacing whole Key class with one-liner).
When you create your own key pair object, you should face a few thing.
First, you should be aware of implementing hashCode() and equals(). You will need to do this.
Second, when implementing hashCode(), make sure you understand how it works. The given user example
public int hashCode() {
return this.x ^ this.y;
}
is actually one of the worst implementations you can do. The reason is simple: you have a lot of equal hashes! And the hashCode() should return int values that tend to be rare, unique at it's best. Use something like this:
public int hashCode() {
return (X << 16) + Y;
}
This is fast and returns unique hashes for keys between -2^16 and 2^16-1 (-65536 to 65535). This fits in almost any case. Very rarely you are out of this bounds.
Third, when implementing equals() also know what it is used for and be aware of how you create your keys, since they are objects. Often you do unnecessary if statements cause you will always have the same result.
If you create keys like this: map.put(new Key(x,y),V); you will never compare the references of your keys. Cause everytime you want to acces the map, you will do something like map.get(new Key(x,y));. Therefore your equals() does not need a statement like if (this == obj). It will never occur.
Instead of if (getClass() != obj.getClass()) in your equals() better use if (!(obj instanceof this)). It will be valid even for subclasses.
So the only thing you need to compare is actually X and Y. So the best equals() implementation in this case would be:
public boolean equals (final Object O) {
if (!(O instanceof Key)) return false;
if (((Key) O).X != X) return false;
if (((Key) O).Y != Y) return false;
return true;
}
So in the end your key class is like this:
public class Key {
public final int X;
public final int Y;
public Key(final int X, final int Y) {
this.X = X;
this.Y = Y;
}
public boolean equals (final Object O) {
if (!(O instanceof Key)) return false;
if (((Key) O).X != X) return false;
if (((Key) O).Y != Y) return false;
return true;
}
public int hashCode() {
return (X << 16) + Y;
}
}
You can give your dimension indices X and Y a public access level, due to the fact they are final and do not contain sensitive information. I'm not a 100% sure whether private access level works correctly in any case when casting the Object to a Key.
If you wonder about the finals, I declare anything as final which value is set on instancing and never changes - and therefore is an object constant.
You can't have an hash map with multiple keys, but you can have an object that takes multiple parameters as the key.
Create an object called Index that takes an x and y value.
public class Index {
private int x;
private int y;
public Index(int x, int y) {
this.x = x;
this.y = y;
}
#Override
public int hashCode() {
return this.x ^ this.y;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Index other = (Index) obj;
if (x != other.x)
return false;
if (y != other.y)
return false;
return true;
}
}
Then have your HashMap<Index, Value> to get your result. :)
Implemented in common-collections MultiKeyMap
Java 7+ contains a new Map.Entry<K,V> class that you can use as key for your map (or entry for your set). Java 9+ also contains a Map.entry(K k, V v) method to easily create new Map.Entry objects.
Usage:
Map<Map.Entry<Integer,Integer>, Integer> map = new HashMap<>();
map.put(Map.entry(1, 2), 0);
There is also Pair<K, V> in javafx.util
Map<Pair<Integer,Integer>, Integer> map = new HashMap<>();
map.put(new Pair(1, 2), 0);
Two possibilities. Either use a combined key:
class MyKey {
int firstIndex;
int secondIndex;
// important: override hashCode() and equals()
}
Or a Map of Map:
Map<Integer, Map<Integer, Integer>> myMap;
Use a Pair as keys for the HashMap. JDK has no Pair, but you can either use a 3rd party libraray such as http://commons.apache.org/lang or write a Pair taype of your own.
Create a value class that will represent your compound key, such as:
class Index2D {
int first, second;
// overrides equals and hashCode properly here
}
taking care to override equals() and hashCode() correctly. If that seems like a lot of work, you might consider some ready made generic containers, such as Pair provided by apache commons among others.
There are also many similar questions here, with other ideas, such as using Guava's Table, although allows the keys to have different types, which might be overkill (in memory use and complexity) in your case since I understand your keys are both integers.
If they are two integers you can try a quick and dirty trick: Map<String, ?> using the key as i+"#"+j.
If the key i+"#"+j is the same as j+"#"+i try min(i,j)+"#"+max(i,j).
You can also use guava Table implementation for this.
Table represents a special map where two keys can be specified in combined fashion to refer to a single value. It is similar to creating a map of maps.
//create a table
Table<String, String, String> employeeTable = HashBasedTable.create();
//initialize the table with employee details
employeeTable.put("IBM", "101","Mahesh");
employeeTable.put("IBM", "102","Ramesh");
employeeTable.put("IBM", "103","Suresh");
employeeTable.put("Microsoft", "111","Sohan");
employeeTable.put("Microsoft", "112","Mohan");
employeeTable.put("Microsoft", "113","Rohan");
employeeTable.put("TCS", "121","Ram");
employeeTable.put("TCS", "122","Shyam");
employeeTable.put("TCS", "123","Sunil");
//get Map corresponding to IBM
Map<String,String> ibmEmployees = employeeTable.row("IBM");
You could create your key object something like this:
public class MapKey {
public Object key1;
public Object key2;
public Object getKey1() {
return key1;
}
public void setKey1(Object key1) {
this.key1 = key1;
}
public Object getKey2() {
return key2;
}
public void setKey2(Object key2) {
this.key2 = key2;
}
public boolean equals(Object keyObject){
if(keyObject==null)
return false;
if (keyObject.getClass()!= MapKey.class)
return false;
MapKey key = (MapKey)keyObject;
if(key.key1!=null && this.key1==null)
return false;
if(key.key2 !=null && this.key2==null)
return false;
if(this.key1==null && key.key1 !=null)
return false;
if(this.key2==null && key.key2 !=null)
return false;
if(this.key1==null && key.key1==null && this.key2 !=null && key.key2 !=null)
return this.key2.equals(key.key2);
if(this.key2==null && key.key2==null && this.key1 !=null && key.key1 !=null)
return this.key1.equals(key.key1);
return (this.key1.equals(key.key1) && this.key2.equals(key2));
}
public int hashCode(){
int key1HashCode=key1.hashCode();
int key2HashCode=key2.hashCode();
return key1HashCode >> 3 + key2HashCode << 5;
}
}
The advantage of this is: It will always make sure you are covering all the scenario's of Equals as well.
NOTE: Your key1 and key2 should be immutable. Only then will you be able to construct a stable key Object.
we can create a class to pass more than one key or value and the object of this class can be used as a parameter in map.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.*;
public class key1 {
String b;
String a;
key1(String a,String b)
{
this.a=a;
this.b=b;
}
}
public class read2 {
private static final String FILENAME = "E:/studies/JAVA/ReadFile_Project/nn.txt";
public static void main(String[] args) {
BufferedReader br = null;
FileReader fr = null;
Map<key1,String> map=new HashMap<key1,String>();
try {
fr = new FileReader(FILENAME);
br = new BufferedReader(fr);
String sCurrentLine;
br = new BufferedReader(new FileReader(FILENAME));
while ((sCurrentLine = br.readLine()) != null) {
String[] s1 = sCurrentLine.split(",");
key1 k1 = new key1(s1[0],s1[2]);
map.put(k1,s1[2]);
}
for(Map.Entry<key1,String> m:map.entrySet()){
key1 key = m.getKey();
String s3 = m.getValue();
System.out.println(key.a+","+key.b+" : "+s3);
}
// }
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (br != null)
br.close();
if (fr != null)
fr.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
You can download it from the below link:
https://github.com/VVS279/DoubleKeyHashMap/blob/master/src/com/virtualMark/doubleKeyHashMap/DoubleKeyHashMap.java
https://github.com/VVS279/DoubleKeyHashMap
You can use double key: value hashmap,
DoubleKeyHashMap<Integer, Integer, String> doubleKeyHashMap1 = new
DoubleKeyHashMap<Integer, Integer, String>();
DoubleKeyHashMap<String, String, String> doubleKeyHashMap2 = new
DoubleKeyHashMap<String, String, String>();
Using org.apache.commons.lang3.tuple.Pair is very easy;
Map<String, Pair<String, Integer>> map= new HashMap<>();
map.put("key", Pair.of("a", 1));
int value = map.get("key").getRight();

Categories

Resources