7

So I have a very odd bug. I stumbled across it when I was originally using a keySet() to iterate over the first 10 keys of a large TreeMap. One of the keys was returning null, which should not be possible as far as my understanding goes. So I wrote the test code below:

int i = 0;
        for (Map.Entry<String, Integer> es : sortedMap.entrySet()){
            if (i >= 10) {
                break;
            }

            if (sortedMap.containsKey(es.getKey())){
                System.out.println(es.getKey() + ":" + sortedMap.get(es.getKey()));
            } else {
                System.out.println("Key " + es.getKey() + " does not exist, yet...");
                System.out.println("This does work: " + es.getKey() + ":" + es.getValue());
                System.out.println("This does NOT work: " + es.getKey() + ":" + sortedMap.get(es.getKey()));
            }
            i++;
        }

And get the following results:

SOAP:967
'excerpt'::679
'type'::679
Key 'author_url': does not exist, yet...
This does work: 'author_url'::679
This does NOT work: 'author_url'::null
'date'::679
Android:437
TLS:295
message:283
server:230
monthly:215
<<<<<<<<<<<<<<<<<<<<DUMPING MAP!
{SOAP=967, 'excerpt':=679, 'type':=679, 'author_url':=679, 'date':=679, Android=437, TLS=295, message=283, server=230, monthly=215...

I cut off the map after the top ten as there is a lot more in there, but all of it is a key with a value.

So my question is this: Why am I getting a null when using the key to directly get(key) from the TreeMap, but the EntrySet returns the correct key and value?

Here is my comparator since I am ordering on Integer:

class ValueComparator implements Comparator<Object> {

  Map<String, Integer> base;
  public ValueComparator(Map<String, Integer> base) {
      this.base = base;
  }

  public int compare(Object a, Object b) {

    if ((Integer) base.get(a) < (Integer) base.get(b)) {
      return 1;
    } else if ((Integer) base.get(a) == (Integer) base.get(b)) {
      return 0;
    } else {
      return -1;
    }
  }
}

And the TreeMap is built as following:

ValueComparator bvc =  new ValueComparator(allMatches);
TreeMap<String, Integer> sortedMap = new TreeMap<String, Integer>(bvc);
//Sort the HashMap
sortedMap.putAll(allMatches);

Where allMatches is a HashMap<String, Integer>

Dennis Sullivan
  • 125
  • 1
  • 10
  • 4
    Are you using an unusual comparator for the TreeMap? If so, can we see its code? It looks from your dump as though you aren't using the default `String` ordering... – Louis Wasserman Feb 24 '12 at 19:54
  • @LouisWasserman Added my comparator. – Dennis Sullivan Feb 24 '12 at 20:02
  • 2
    Why do you have a constructor and a member in that class? The comparator is normally called for each element so you should not reference the resulting map. Your entire code would be useful. – tom Feb 24 '12 at 20:04
  • 1
    [This question](http://stackoverflow.com/questions/2694526/consistent-equals-results-but-inconsistent-treemap-containskey-result) is potentially relevant to your problem... – smessing Feb 24 '12 at 20:05
  • 1
    What's the problem with _One of the keys was returning null_ ? TreeSet allows nulls – Oleg Mikheev Feb 24 '12 at 20:12
  • `TreeMap.get(entry.getKey())` should return the same thing as `entry.getValue()`, assuming `entry` came from the input `map`. That's the problem. – Louis Wasserman Feb 24 '12 at 20:35

4 Answers4

8

From the order of iteration your TreeMap shows, it is definetly the case that you used a custom Comparator. [Otherwise the iteration would have been in lexicographical order]

Note that according to the javadocs:

The implementor must ensure that sgn(compare(x, y)) == -sgn(compare(y, x)) for all x and y. (This implies that compare(x, y) must throw an exception if and only if compare(y, x) throws an exception.)

The implementor must also ensure that the relation is transitive: ((compare(x, y)>0) && (compare(y, z)>0)) implies compare(x, z)>0.

Finally, the implementer must ensure that compare(x, y)==0 implies that sgn(compare(x, z))==sgn(compare(y, z)) for all z.

If your Comparator does not apply these rules - the behavior is not defined, as might show strange results - as you see.

EDIT: [as response to the editted question]
Your compartor uses identity [operator==] to check two Integers.
Note that Integer is an object - and thus operator== will return true only if it is the same object.
You should use equals() to check if two integers are identical - or even better - use Integer.compareTo()

amit
  • 175,853
  • 27
  • 231
  • 333
  • Using compareTo causes the TreeMap to collapse on any value that is equal. `{monthly=215, server=230, message=283, TLS=295, Android=475, excerpt=679, SOAP=967}` – Dennis Sullivan Feb 24 '12 at 21:00
  • Indeed, and that's a hint that your code is even more broken. You can't use `TreeMap` at all, not like you're trying to use it. – Louis Wasserman Feb 24 '12 at 21:04
  • @DennisSullivan: In addition to what Louis said: Read the attached java docs. You must make sure your Comparator satisfies the terms written - otherwise the behavior is not defined. – amit Feb 24 '12 at 21:06
  • Up voted for helping my line of thinking to arrive at the correct answer. – Dennis Sullivan Feb 27 '12 at 19:07
3

Your biggest problem is that your use of == instead of .equals in your value comparator is breaking things, because different keys are getting mapped to different Integer objects with the same intValue(), which is throwing off even more things unpredictably.

But if you fixed that, then your TreeMap wouldn't let you insert multiple keys with the same value, which is almost certainly also causing subtle breakages.

A better solution would be something like this, but basically, you should fill in a map without sorting by values, sort the entrySet, and then copy the entries (in order) to a map like LinkedHashMap that doesn't need a comparator, but just keeps entries in the order of insertion.

You might be able to change your comparator so that if the values are the same, it'll compare the keys as well. This would at least let you insert multiple keys with the same value...but it's still a really hacky solution that's much riskier than a LinkedHashMap-based solution as described above.

Community
  • 1
  • 1
Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
1

Problem solved:

class ValueComparator implements Comparator<Object> {

Map<String, Integer> base;

public ValueComparator(Map<String, Integer> base) {
    this.base = base;
}

public int compare(Object a, Object b) {

    if (((Integer) base.get(a)).intValue() < ((Integer) base.get(b)).intValue()) {
        return 1;
    } else if ( ((Integer) base.get(a)).intValue() == ((Integer) base.get(b)).intValue()) {
        return ((String)a).compareTo(((String)b));
    } else {
        return -1;
    }
}
}

This comes with the additional benefit of bringing back keys with the same value in alphabetical order.

Dennis Sullivan
  • 125
  • 1
  • 10
0

You should simply have:

class ValueComparator implements Comparator<Integer> {


  public int compare(Integer a, Integer b) {
      return a.compareTo(b);
  }
}

Next you need to initialize your treemap with the comparator and add all your items:

Treemap

tom
  • 2,735
  • 21
  • 35
  • He's using it to compare the values, which isn't going to work with a `TreeMap`, not really =( – Louis Wasserman Feb 24 '12 at 20:16
  • You are right. I thouht he was sorting on the keys (as usual) but he is sorting on values. I misread the question. All I can say is that sorting on values is not normally done in a map. Lists are better suited for that. – tom Feb 24 '12 at 20:22