简体   繁体   中英

Java HashMap strange behavior value changing after if statement

I have two HashMaps. mapA.keySet() is a subset of mapB.keySet() . I want to print every key for which mapA.get(key) != mapB.get(key) . But some strange behavior occurs in the code below:

private static void printMissingNums(int[] a, int[] b) {
    Map<Integer, Integer> mapA = intArrToMap(a);
    Map<Integer, Integer> mapB = intArrToMap(b);

    Set<Integer> missingNums = new TreeSet<Integer>();
    for (int key : mapA.keySet()) {
        //This version does not work!
        if (mapA.get(key) != mapB.get(key)) {
            missingNums.add(key);
        }

        /* This version works (if I comment out the if statement above
           and remove the comments around this block of code)
        int valA = mapA.get(key);
        int valB = mapB.get(key);
        if (valA != valB) {
            missingNums.add(key);
        }
        */
    }

    // Unrelated to the strange behavior
    for (int key : mapB.keySet()) {
        if (!mapA.containsKey(key)) {
             missingNums.add(key);
        }
    }

    for (int i : missingNums) {
        System.out.print(i + " ");
    }
}

I get strange behavior when I use the first if statement and want to know what is going on behind the scenes / why it's not working as I think it should. For a specific input that I have access to, it prints 3 numbers called x, y, z. I check the HashMaps and find that mapA.get(x) != mapB.get(x) and mapA.get(y) != mapB.get(y) but mapA.get(z) == mapB.get(z) .

I tried printing the values before and after the if statement and the values are equal but it somehow goes into the if statement.

The commented out version works as I expect. It prints out only x and y. What is happening? Why does it seem like the HashMap values are changing even though I am not changing anything?

Here is the input: http://pastebin.com/JyYxspjx . The first line is the number of elements in the first array, followed by space separated integers. the next line after those is the number of elements in the second array, followed by space separated integers.

How come 8622 is the only key that has the same value but the comparison is false?

Do not use == , != for Object compare.use equals() instead.

The flowing 2 integer are different object:

Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
System.out.println(i1==i2);//false

change the if statement to :

if (mapA.get(key).equlas(mapB.get(key))) {

The difference in comparisons ( == versus foo.equals(Obj obj) ) is based on the object's particular implementation of the equals() method. The operator == means same object and in the default case .equals() does the same comparison. You have to override the equals method to change this default behavior.

In the Java Integer class it overrides the equals method changing the behavior from same object to equating the primitive values:

// From the java source
public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

Here you can see the Integer class is actually comparing class type and then primitive values for equality. In this case the Integer == Integer comparison is different than Integer.equals(Integer) comparison.

In case you are curious, the Java Object class equals implementation is:

// From the Java source code
public boolean equals(Object obj) {
    return (this == obj);
}

Since everything ultimately extends the Object class in java you are always getting "same object" for equals unless your class or an inherited class overrides the equals method.

On a side node, if you ever override & implement equals yourself, be sure to also override & implement hashcode as well (most IDE's will have auto code generation for this and warnings if you do one & not the other). A consequence of not overriding hashcode when overriding equals means your HashMap / HashSet collections will likely not find the "equal" object in the map/set since it won't necessarily hash into the same bucket.

Another approach would be to use the Set operation methods. If you don't care about modification to the underlying map you could do something like: (keep in mind this actually will modify the keyset/underlying map)

Set<Integer> uniqKeysInA = mapA.keySet().removeAll(mapB.keyset())

If you cared about modification/maintaining the original, you'd need a defensive copy of mapA's keyset to operate on:

Set<Integer> uniqKeysInA = (new HashSet<Integer>(mapA.keySet())).removeAll(mapB.keySet())

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM