6

I've got a HashMap<Point, T> data structure which holds several points that are mapped to other instances of the class T. This map is given some default values when my class is instantiated like this:

T t1 = new T();
T t2 = new T();
Point p1 = new Point(0, 1);
Point p2 = new Point(0, 2);

HashMap<Point, T> map = new HashMap<Point, T>();
static {
    map.put(p1, t1);
    map.put(p2, t2);
}

In my code, I will be receiving events that contain an x and an y value. When I receive one of these events I'm trying to create a new Point object with the x and y that is passed and then retrive the value from the map like this:

Point p = new Point(event.getX(), event.getY); // Assume (x, y) = (0, 1) (p1)
if(p.equals(p1)    
    T t = map.get(p);

Although p is equal to p1 in this case (with (x, y) = (0, 1) I am getting a null value back from the map. I assume that is because the hashCode() method in Point (Point2D) uses something else than equals to calculate an unique hash, which it should to prevent collisions.

My question is: How can I retrive the value from the map using the new instance p? Is there another data structure that would fit the use case?

I guess I could use toString() or some other mapping like HashMap<String, T> or perhaps I would extend the Point class and Override the hashCode() method to suit my purposes. These ways feel "hacky" though, if there is a cleaner way I'd love to hear it.

span
  • 5,144
  • 5
  • 47
  • 96

4 Answers4

5

According to the Java documentation,

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 appears that in your case two Point objects are equal (as per the equals method) but their hash codes are different. This means that you need to fix your equals and hashCode functions to be consistent with each other.

Sergey Kalinichenko
  • 675,664
  • 71
  • 998
  • 1,399
  • Thanks, but Point is the Point in the Java API so if I'm to fix it I need to extend it. Also, why would they go against their own recommendation? http://docs.oracle.com/javase/6/docs/api/java/awt/Point.html – span Jan 29 '13 at 18:55
  • 2
    Point is Java 1.0; it predates the Joshua Bloch "Effective Java" recommendations for how to properly override equals and hashCode by almost a decade. That's why it's not consistent with today's best practice. – duffymo Jan 29 '13 at 18:56
  • That would explain a lot. Thanks! Will override I guess :/ – span Jan 29 '13 at 18:58
  • 2
    @span That's not a recommendation, it is a requirement. I think that the version from the latest JDK overrides the hash code as well (see [this question](http://stackoverflow.com/q/9251961/335858) for a code snippet showing how it is done). There may be a problem with comparison of values of type `double`, though. – Sergey Kalinichenko Jan 29 '13 at 19:00
  • 2
    Unless you need your `Point` object to tie into existing Java libraries, you may find it better to roll your own `Point` class that does exactly what you want, rather than extend Java's class. – dimo414 Jan 29 '13 at 19:18
  • I agree with dimo414. Write your own - it's not that much effort. Overriding buys you nothing of value that I can see, unless you pass those values to libraries that require the JDK class. – duffymo Jan 29 '13 at 19:42
2

Can you try writing a self contained example like the follow we can run

Map<Point, String> map = new LinkedHashMap<>();
for (int i = 0; i < 10; i++)
    for (int j = 0; j < 10; j++)
        map.put(new Point(i, j), "(" + i + "," + j + ")");

// test the map
int misMatches = 0;
for (int i = 0; i < 10; i++)
    for (int j = 0; j < 10; j++) {
        String expected = "(" + i + "," + j + ")";
        String text = map.get(new Point(i, j));
        if (!expected.equals(text)) {
            System.err.println("Expected <" + expected + "> but got <" + text + ">");
            misMatches++;
        }
    }
System.out.println(misMatches + " mis-matches found.");

prints

0 mis-matches found.
Peter Lawrey
  • 498,481
  • 72
  • 700
  • 1,075
  • Very strange, I will look into this. Are you using Java 6 or 7 for that? – span Jan 29 '13 at 19:03
  • I'm getting same results here in my home environment, I will try this tomorrow at the other environment to investigate further. – span Jan 29 '13 at 19:13
  • I was using Java 8, but I wouldn't expect it to be any different. Point is a very old class. (more than 10 years) – Peter Lawrey Jan 29 '13 at 19:16
2

I dont think there is any problem with equals() or hashcode() of Point class.Try this:

public static void main(String args[]) {

        Map<Point, Integer> map = new HashMap<Point, Integer>();
        Point p1 = new Point(0, 1);
        Point p2 = new Point(0, 2);
        map.put(p1,1);
        map.put(p2,2);
        Point p = new Point(0, 1);
        if(p.equals(p1)){
            System.out.println(map.get(p));
        }
        else{
            System.out.println("not");
        }



    }

It is producing the correct result.

I guess you are not initialising the map properly.

Renjith
  • 3,130
  • 16
  • 39
1

hashCode() should use the same attributes as equals() in order to be consistent.

See also: What issues should be considered when overriding equals and hashCode in Java?

Community
  • 1
  • 1
Gothmog
  • 803
  • 1
  • 8
  • 19
  • So you're recommending to extend Point or Point2d and override hashCode() if I want to use a map? No other way? – span Jan 29 '13 at 18:51
  • Since I'm only using Java SE classes for this I'm a bit confused to why it's not working and only returning null. – span Jan 29 '13 at 18:57