2

I have a TreeSet with elements, and according to:

http://docs.oracle.com/javase/7/docs/api/java/lang/Comparable.html#compareTo%28T%29

[ The natural ordering for a class C is said to be consistent with equals if and only if e1.compareTo(e2) == 0 has the same boolean value as e1.equals(e2) for every e1 and e2 of class C. Note that null is not an instance of any class, and e.compareTo(null) should throw a NullPointerException even though e.equals(null) returns false. ]

Element class have a not null safe compareTo method

I have the following code working on Java 1.5, but not in Java 1.7

  • Why I need to do a Null Safe compareTo? and why javadoc say that?
  • Why compareTo method was triggered at first add call in Java 1.7 but no in 1.5?
@Test
public void simpleTest() {
    try {
        Collection<Element> set = new TreeSet<Element>();
        Element cv = new Element(null);
        set.add(cv);//first add throws NPE (calling to compareTo())
    } catch (Exception e) {
        e.printStackTrace();
    }
}

private class Element implements Comparable<Element> {
    private final String attr;

    public Element(String attr) {
        super();
        this.attr = attr;
    }

    @Override
    public int hashCode() {
        System.out.println("executing hashCode...");
        final int prime = 31;
        int result = 1;
        result = prime * result + getOuterType().hashCode();
        result = prime * result + ((attr == null) ? 0 : attr.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        System.out.println("executing equals...");
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Element other = (Element) obj;
        if (!getOuterType().equals(other.getOuterType()))
            return false;
        if (attr == null) {
            if (other.attr != null)
                return false;
        } else if (!attr.equals(other.attr))
            return false;
        return true;
    }

    private CatalogoActionTest getOuterType() {
        return CatalogoActionTest.this;
    }

    public int compareTo(Element o) {
        System.out.println("executing compareTo...");
        //throw NPE when attr is null
        return this.attr.compareTo(o.attr);//line 182
    }
}

I want to understand if compareTo needs be null safe, or the problem is construct a new object with invalid data.

This is the stacktrace:

    java.lang.NullPointerException
    at com.MyTest$Element.compareTo(MyTest.java:182)
    at com.MyTest$Element.compareTo(MyTest.java:138)
    at java.util.TreeMap.compare(TreeMap.java:1188)
    at java.util.TreeMap.put(TreeMap.java:531)
    at java.util.TreeSet.add(TreeSet.java:255)
Omar Hrynkiewicz
  • 502
  • 1
  • 8
  • 21

2 Answers2

3

Hm. Funny. If you check line 531 of TreeMap you'll see that it compares key with itselft:

compare(key, key);

So basically it invokes

cv.compareTo(cv);

And it crushes because attr in cv is null. Class Element doesn't implement compareTo correctly. I suppose it must return 0 if you comparing object with itself itself of throwing NPE.

Mikita Belahlazau
  • 15,326
  • 2
  • 38
  • 43
  • 4
    I think that's part of the new consistency checks added in Java 7. The sorted collections are much more failfast with non-conformant `compareTo` methods. `me.compareTo(me)` should always be `0`. – Boris the Spider Dec 02 '13 at 16:38
  • +1. The technical term is that `compareTo` must be "consistent with equals". See `Comparator` javadoc for a definition. – Stuart Marks Dec 02 '13 at 16:50
1

TreeSet / TreeMap behaviour changed in Java 7. Consider this main method:

import java.util.TreeSet;

public class C {

  public static void main(String[] args) {
    TreeSet<Object> ts = new TreeSet<Object>();
    ts.add(null);
    System.out.println("TreeSet size is: " + ts.size());
  }

}

Runs fine on Java 6:

$ java -showversion -cp . C
java version "1.6.0_45"
Java(TM) SE Runtime Environment (build 1.6.0_45-b06)
Java HotSpot(TM) 64-Bit Server VM (build 20.45-b01, mixed mode)

TreeSet size is: 1

Blows up on Java 7:

$ java -showversion -cp . C
java version "1.7.0_55"
Java(TM) SE Runtime Environment (build 1.7.0_55-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.55-b03, mixed mode)

Exception in thread "main" java.lang.NullPointerException
    at java.util.TreeMap.compare(TreeMap.java:1188)
    at java.util.TreeMap.put(TreeMap.java:531)
    at java.util.TreeSet.add(TreeSet.java:255)
    at C.main(C.java:7)
Julius Musseau
  • 4,037
  • 23
  • 27