Michael John Mason
Michael John Mason

Reputation: 13

Using objects in HashMaps as a Key

so I have a small issue that I cant wrap my head around.

I have a need to store a class object as a key in a map and for it to be retrieved later by a newly created object looking against the map. I have a class that implements Cloneable and overrides toString, hashCode and equals, but it seems that the object is unique and no matter how I create the object to use as a key to retrieve the value from the map, the new object that should match the key, doesn't.

The Object Class:

package com.keneti.tekkit.objects;

import org.bukkit.block.Block;
import org.bukkit.inventory.ItemStack;

import com.keneti.main.KenetiPrereq;

/** Simple object representing an block by the ID:DATA.
 * 
 * @author Michael Mason */
public class SimpleBlock implements Cloneable {
    protected final int id;
    protected byte data;
    
    /** Creates a new SimpleBlock object with a data value of zero.
     * 
     * @param id The Block ID. */
    public SimpleBlock(int id) {
        this.id = id;
        this.data = 0;
    }
    
    /** Creates a new SimpleBlock object.
     * 
     * @param id The Block ID.
     * @param data The block data to set. */
    public SimpleBlock(int id, byte data) {
        this.id = id;
        this.data = data;
    }
    
    /** Creates a new SimpleBlock object from a {@link Block}.
     * 
     * @param block A bukkit block. */
    public SimpleBlock(Block block) {
        this(block.getTypeId(), block.getData());
    }
    
    /** Creates a new SimpleBlock object from an {@link ItemStack}.
     * 
     * @param itemStack A bukkit ItemStack. */
    public SimpleBlock(ItemStack itemStack) {
        this(itemStack.getTypeId(), itemStack.getData().getData());
    }
    
    /** Gets the ID of this block.
     * 
     * @return The ID of this block. */
    public int getId() {
        return id;
    }
    
    /** Gets the Data for this block.
     * 
     * @return The Data of this block. */
    public byte getData() {
        return data;
    }
    
    /** Sets the Data for this block.
     * 
     * @param data The Data to set for this block. */
    public void setData(byte data) {
        this.data = data;
    }
    
    @Override
    public Object clone() {
        try {
            SimpleBlock s = (SimpleBlock) super.clone();
            return s;
        }
        catch (CloneNotSupportedException e) {
            KenetiPrereq.journal.fine("CloneNotSupportedException: SimpleBlock " + this.toString());
            throw new Error(e);
        }
    }
    
    @Override
    public String toString() {
        return "{SimpleBlock:" + this.id + ":" + this.data + "}";
    }
    
    @Override
    public int hashCode() {
        return this.toString().hashCode();
    }
    
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof SimpleBlock) return (this.id == ((SimpleBlock) obj).id) && (this.data == ((SimpleBlock) obj).data);
        if (obj instanceof ItemStack) return (this.id == ((ItemStack) obj).getTypeId()) && (this.data == ((ItemStack) obj).getData().getData());
        if (obj instanceof Block) return (this.id == ((Block) obj).getTypeId()) && (this.data == ((Block) obj).getData());
        return false;
    }
    
}

Running Code

// The Map holding the value
private LinkedHashMap<SimpleBlock, LinkedList<Location>> locationsmap;

// I create the SimpleBlock object like this
SimpleBlock sb = new SimpleBlock(758, (byte) 14);

// I add the locations with the SimpleBlock object as the key.
locationsmap.put(sb, locations);

Later, in another class:

// so after getting the map in a local variable..
LinkedList<Location> locations = locationsMap.get(new SimpleBlock(758, (byte) 14));

When I try to get the object using the same creation data, it doesn't see the object in the map (although I see it when stepping the code, its in there for sure)

My question is, how doesn't this currently work?, I'm sure i've done the required steps to make the object equal?

Thank you in advance :D

Upvotes: 1

Views: 127

Answers (1)

Chii
Chii

Reputation: 14746

The implementation of equals and hashcode does not satisfy the contract required (see http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#equals%28java.lang.Object%29).

In a nutshell, the SimpleBlock object can equal to another object, but have a different hashcode

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.

fix that, and i think you'll fix your problem.

Edit: also, use a library to build the equals and hashcode methods will make it easier: https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/builder/EqualsBuilder.html and https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/builder/HashCodeBuilder.html

Upvotes: 1

Related Questions