1

I am trying to add data to my hashmap if the key does not already exist in the map. For some reason, even if the key does exist, the hashmap adds it anyways. I have no idea why this is happening. My addEntity method is the problem. I am trying to detect of the key is already in the hashmap, and if it is, then do nothing. However, it for some reason will always add the key, no matter if the key already exists.

My data file:

package timeTraveler.mechanics;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import net.minecraft.entity.EntityLiving;

public class PathingData 
{
    /**
     * Entity data array
     */
    public static Map<String[], List<int[]>> allEntityData;

    public PathingData()
    {
        allEntityData = new HashMap<String[], List<int[]>>();
    }
    /**
     * Adds an entity UUID (Unique ID)and MobType to the entity data ArrayList.  If the entity already exists inside of the ArrayList, then it skips it.
     * @param uuid
     */
    public void addEntity(String[] entityData)
    {       
        System.out.println(entityData[0]);
        if(!allEntityData.containsKey(entityData))
        {
            System.out.println("Adding entity!");
            allEntityData.put(entityData, new ArrayList<int[]>());
        }
        else
        {
            System.out.println("ENTITY ALREADY EXISTS IN ARRAY");
        }
    }
    /**
     * Adds data (X, Y, and Z) to the corresponding UUID (Unique ID) for the entity.  If the entity's UUID does not exist, then it prints out a line that says the UUID cannot be found.
     * @param uuid
     * @param data
     */
    public void addData(String[] entityData, String data)
    {
        System.out.println(entityData[0]);
        if(allEntityData.containsKey(entityData))
        {
            System.out.println("Adding data to entity!");
            int[] rawData = new int[3];
            String[] pureData = data.split(",");

            rawData[0] = Integer.parseInt(pureData[0]);
            rawData[1] = Integer.parseInt(pureData[1]);
            rawData[2] = Integer.parseInt(pureData[2]);

            List<int[]> entityLocData = allEntityData.get(entityData);
            entityLocData.add(rawData);
            allEntityData.put(entityData, entityLocData);
        }
        else
        {
            System.out.println("ENTITY DOES NOT EXIST IN ARRAY! :(");
            //addEntity(entityData);
        }
    }
    /**
     * Gets the data for a specific UUID (Unique ID) for an entity.
     * @param uuid
     * @return
     */
    public List<int[]> getDataForUUID(String[] entityData)
    {
        List<int[]> entityLoc = allEntityData.get(entityData);
        return entityLoc;
    }
    /**
     * Clears all entities and their corresponding data from the map.
     */
    public void clearAllEntitiesAndData()
    {
        allEntityData.clear();
    }

    /**
     * Checks if entity exists inside of array
     * @param uuid
     * @return
     */
    public boolean doesEntityExist(String[] entityData)
    {
        List<int[]> entityLoc = allEntityData.get(entityData);
        if(entityData != null)
        {
            return true;
        }
        return false;
    }
}

I have made sure that there is only one instance of the variable, and I always refer to that one variable in my .addEntity and .addData. Any ideas?

EDIT: I have just now tried to implement the suggestion proposed. However, it still just prints out the same thing, with timetraveler.core.StringArrayHolder@0 instead of the array. Here is the modified code:

package timeTraveler.mechanics;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import timeTraveler.core.StringArrayHolder;

import net.minecraft.entity.EntityLiving;

public class PathingData 
{
    /**
     * Entity data array
     */
    public static Map<StringArrayHolder, List<int[]>> allEntityData;

    public PathingData()
    {
        allEntityData = new HashMap<StringArrayHolder, List<int[]>>();
    }
    /**
     * Adds an entity UUID (Unique ID)and MobType to the entity data ArrayList.  If the entity already exists inside of the ArrayList, then it skips it.
     * @param uuid
     */
    public void addEntity(StringArrayHolder entityData)
    {       
        System.out.println(entityData);
        if(!allEntityData.containsKey(entityData))
        {
            System.out.println("Adding entity!");
            allEntityData.put(entityData, new ArrayList<int[]>());
        }
        else
        {
            System.out.println("ENTITY ALREADY EXISTS IN ARRAY");
        }
    }
    /**
     * Adds data (X, Y, and Z) to the corresponding UUID (Unique ID) for the entity.  If the entity's UUID does not exist, then it prints out a line that says the UUID cannot be found.
     * @param uuid
     * @param data
     */
    public void addData(StringArrayHolder entityData, String data)
    {
        System.out.println(entityData);
        if(allEntityData.containsKey(entityData))
        {
            System.out.println("Adding data to entity!");
            int[] rawData = new int[3];
            String[] pureData = data.split(",");

            rawData[0] = Integer.parseInt(pureData[0]);
            rawData[1] = Integer.parseInt(pureData[1]);
            rawData[2] = Integer.parseInt(pureData[2]);

            List<int[]> entityLocData = allEntityData.get(entityData);
            entityLocData.add(rawData);
            allEntityData.put(entityData, entityLocData);
        }
        else
        {
            System.out.println("ENTITY DOES NOT EXIST IN ARRAY! :(");
            //addEntity(entityData);
        }
    }
    /**
     * Gets the data for a specific UUID (Unique ID) for an entity.
     * @param uuid
     * @return
     */
    public List<int[]> getDataForUUID(StringArrayHolder entityData)
    {
        List<int[]> entityLoc = allEntityData.get(entityData);
        return entityLoc;
    }
    /**
     * Clears all entities and their corresponding data from the map.
     */
    public void clearAllEntitiesAndData()
    {
        allEntityData.clear();
    }

    /**
     * Checks if entity exists inside of array
     * @param uuid
     * @return
     */
    public boolean doesEntityExist(StringArrayHolder entityData)
    {
        List<int[]> entityLoc = allEntityData.get(entityData);
        if(entityData != null)
        {
            return true;
        }
        return false;
    }
}

and the wrapper:

package timeTraveler.core;

import java.util.ArrayList;

public class StringArrayHolder 
{
    private String[] data;

    public StringArrayHolder()
    {
        data = new String[2];
    }

    public void setData(String[] data)
    {
        this.data = data;
    }
    public String[] getData()
    {
        return this.data;
    }

    @Override
    public int hashCode() 
    {
        return 0;
        //...
    }

    @Override
    public boolean equals(Object o)
    {
        if(data.equals(o))
        {
            return true;
        }
        return false;
        //...
    }

}
Charsmud
  • 183
  • 1
  • 4
  • 19
  • possible duplicate of [Can a java array be used as a HashMap key](http://stackoverflow.com/questions/16839182/can-a-java-array-be-used-as-a-hashmap-key) – Raedwald Feb 26 '15 at 13:08

1 Answers1

13

The problem is that arrays don't override equals nor hashCode methods from Object class, thus even if you add a new String[] with the same values, it will be a different key in your map.

A possible solution would be creating a wrapper class that will hold the String[] for you and override the equals and hashCode methods there.

public class MyStringArrayHolder {

    private String[] data;

   //class constructor...

   //getters and setters for the array...

    @Override
    public int hashCode() {
        //...
    }

    @Override
    public boolean equals(Object o) {
        //...
    }
}

For the implementations of equals and hashCode methods, you can use Arrays#equals and Arrays#hashCode in this wrapper class.


From your comment:

My addEntity method is the problem. I am trying to detect of the key is already in the hashmap, and if it is, then do nothing. However, it for some reason will always add the key, no matter if the key already exists.

This is what I've explained above. The method Map#containsKey clearly states this:

returns true if and only if this map contains a mapping for a key k such that (key==null ? k==null : key.equals(k))

Since arrays does not override Object#equals, you won't have two similar array keys even if they have the same elements in the same position.


EDIT: based on your current edit, the problems are in the equals and hashCode methods implementation. I've made a basic implementation of the MyStringArrayHolder class and copied/pasted the code of the PathingData class. This works as expected (at least for this case):

class MyStringArrayHolder {
    private final String[] data;
    //I do not want any client could change the array reference
    //this also explains why this field doesn't have a setter
    public MyStringArrayHolder(String[] data) {
        this.data = data;
    }
    public String[] getData() {
        return this.data;
    }
    @Override
    public int hashCode() {
        return Arrays.hashCode(data);
    }
    @Override
    public boolean equals(Object o) {
        if (o == null) return false;
        if (o == this) return true;
        if (o instanceof MyStringArrayHolder) {
            MyStringArrayHolder other = (MyStringArrayHolder)o;
            return Arrays.equals(this.data, other.data);
        }
        return false;
    }
    //just to print in console for testing purposes
    @Override
    public String toString() {
        return Arrays.deepToString(data);
    }
}

public class PathingData {
    //removed the static modifier, not really sure why you need it like that
    public Map<MyStringArrayHolder, List<int[]>> allEntityData;
    //current class implementation...
    //just to print in console for testing purposes
    @Override
public String toString() {
    return allEntityData.toString();
}
public static void main(String[] args) {
    PathingData pathingData = new PathingData();
    String[] example1 = { "hello", "world" };
    String[] example2 = { "luiggi", "mendoza" };
    String[] example3 = { "hello", "world" };
    MyStringArrayHolder holder1 = new MyStringArrayHolder(example1);
    MyStringArrayHolder holder2 = new MyStringArrayHolder(example2);
    MyStringArrayHolder holder3 = new MyStringArrayHolder(example3);
    pathingData.addEntity(holder1);
    pathingData.addEntity(holder2);
    pathingData.addEntity(holder3);
    pathingData.addData(holder1, "1,2,3");
    pathingData.addData(holder2, "4,5,6");
    pathingData.addData(holder3, "7,8,9");
    System.out.println(pathingData);
}
}

Output:

Adding entity!
Adding entity!
ENTITY ALREADY EXISTS IN ARRAY
Adding data to entity!
Adding data to entity!
Adding data to entity!
{[luiggi, mendoza]=[[I@35087359], [hello, world]=[[I@5a7691c0, [I@1e5b02a6]}

Note: the last line containing [I@35087359 is the current hash code of the int[]. I would recommend to change from List<int[]> to List<List<Integer>>, but this implementation is outside the scope of the question :).

Luiggi Mendoza
  • 85,076
  • 16
  • 154
  • 332
  • My addEntity method is the problem. I am trying to detect of the key is already in the hashmap, and if it is, then do nothing. However, it for some reason will always add the key, no matter if the key already exists. Sorry if I didn't make that clear in the OP. – Charsmud Sep 21 '13 at 16:35
  • 1
    @Charsmud and this post tells you exactly why that is. – Boris the Spider Sep 21 '13 at 16:36
  • 1
    Why not use a `HashMap, List>`? I'm fairly sure ArrayList implements `hashCode()` and `equals()`. – azz Sep 21 '13 at 16:37
  • 1
    Luiggi, you should mention the `Arrays.equals` and `Arrays.hashCode` methods that are provided in the JDK for exactly this use case. – Boris the Spider Sep 21 '13 at 16:37
  • 4
    Don't need a wrapper, just use `Arrays.asList` – yshavit Sep 21 '13 at 16:41
  • @yshavit are you sure that list implementation overrides `equals` and `hashCode` accordingly? – Luiggi Mendoza Sep 21 '13 at 16:48
  • @yshavit That would work if the elements are in the same order [but not if you don't care about order](http://stackoverflow.com/questions/1075656/simple-way-to-find-if-two-different-lists-contain-exactly-the-same-elements). – Nicole Sep 21 '13 at 16:49
  • @NickC that's why I said you *can*, not you *must* :). – Luiggi Mendoza Sep 21 '13 at 16:50
  • @LuiggiMendoza It [extends](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/Arrays.java#Arrays.asList%28java.lang.Object%5B%5D%29) [AbstractList](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/AbstractList.java#AbstractList), so, yes. Also my last comment was to yshavit about Arrays.asList, not your answer. Your answer is good, but the information about orderless comparison is also good to know. – Nicole Sep 21 '13 at 16:52
  • @NickC that's true for OpenJDK, probably not for other vendors like Oracle JRockit or IBM JVM. The contract doesn't specify it. – Luiggi Mendoza Sep 21 '13 at 16:53
  • I'm a little confused about the hashCode()... Also, I just implemented this, and in the console this prints out repeatedly: 2013-09-21 09:54:08 [INFO] [STDOUT] Adding entity! 2013-09-21 09:54:08 [INFO] [STDOUT] timeTraveler.core.StringArrayHolder@0 2013-09-21 09:54:08 [INFO] [STDOUT] Adding data to entity! I have a feeling this is what hashCode() is for, but I have no idea how to utilize it. – Charsmud Sep 21 '13 at 16:55
  • @Charsmud you must implement the `hashCode` method as well since `HashMap` uses `key#hashCode` internally to store/identify the key. – Luiggi Mendoza Sep 21 '13 at 16:56
  • @Charsmud it will be great if you edit your question and **add** the new proposed solution to get more accurate help. Please **do not remove** any of the current content, otherwise this answer would be invalid. – Luiggi Mendoza Sep 21 '13 at 16:57
  • I think you just lost me there... I'm a little confused on how I would implement that. – Charsmud Sep 21 '13 at 16:59
  • *Probably not*? I'd be extremely surprised, but given that I know nothing about either, you could be right. I guess that's kind of a problem with Java, you can't make overriding `equals` part of the interface contract. – Nicole Sep 21 '13 at 17:01
  • @Charsmud answer updated. The problem was in your `equals` and `hashCode` implementations in the `StringArrayHolder` class. – Luiggi Mendoza Sep 21 '13 at 17:15
  • 1
    @LuiggiMendoza Sorry, just saw your question about `Arrays.asList`. It does implement `equals` and `hashCode`, yes. In fact, the behavior of both of those are specified in the `List` interface, so _all_ correct implementations of `List` must implement those methods (and to the specified behavior). – yshavit Sep 22 '13 at 16:18