简体   繁体   中英

Java: null safe compareTo method

It has been asked before, but I have not found a decent implementation with an explanation.

public int compareTo(Object o)
{
    if (this == null || o == null)
    { 
        return 0;
    }
    Tok tmp = (Tok) o;      
    if (this.rang < tmp.rang)
    {
        return -1;
    } else if (this.rang > tmp.rang ) {
        return 1;
    } else {
        return 0;
    }
}

I read two similar questions that I found yet; they insist on implementing another method. I do not understand why this should not work. The method gets an extra object and it checks if its a valid instance or null , if null simply return 0 ; what would be the easiest way to implement null-safe compareTo .

The implementation that worked for me was:

public int compareTo(Object o)
{
    if (o == null)
    { 
        return 0;
    }
    Tok tmp = (Tok) o;      
    if (this.rang < tmp.rang)
    {
        return -1;
    } else if (this.rang > tmp.rang ) {
        return 1;
    } else {
        return 0;
    }
}

It's not the optimal implementation one should look in to what good people posted here as answers. For my particular case this was decent enough as this is never null yet the object received can be null and the initial implementation states if either is null return 0. So if given object is null 0 is returned.

Personally, I like Guava's Ordering for null-safe comparing. You can specify #nullsFirst() or #nullsLast() to avoid NullPointerException s.

Other important notes, mostly from the comments:

  • this is never null in Java
  • Consider using Guava's ComparisonChain if you're implementing a fine-grained compareTo()
  • When implementing Comparable , be sure to specify the type parameter so you get compile-time type safety and don't have to use instanceof or casts:

     class Tok implements Comparable<Tok> { // snip public int compareTo(Tok other) { // snip } } 

Returning 0 would imply that this and o are equal, which isn't true if o is null. Also, this will never be null.

It's application-dependent, of course. You may want to have an object that should be equal to null. What you return there is up to you, but if you're looking for a general null safe method, it isn't really ideal.

To be completely generic, I would check if o is null and, if so, throw some sort of Exception.

I am not satisified with the other answers:
You should NOT check for null in compareTo.
It is requested that it throws a NullPointerException, otheriwse you will mess up your Trees and have difficulties finding why your TreeMap does not work.

A very recommendable method:

 public int compareTo(Tok other) { 
    int thisRang = this.rang; 
    int otherRang = other.rang; 
    return (thisRang < otherRang ? -1 : (thisRang == otherRang ? 0 : 1)); 
  } 
  public int compareTo(Object other) { 
    return compareTo((Tok)other); 
  } 

Further to make it perfect class Tok should be final! (Otherwise you could have problems when you subclass from Tok. (Sun made that Error in Class Date)

final class Tok {
    int rang;
}

Dealing with compare and equals is not always easy, consider using instead of Trees (TreeMap) a HashMap, then you dont have to implement compareTo. You should implement hashCode, where you simply return this.rang.

Finally its is highly recomended, but not mandatory to implement equals()

public boolean equals(Object obj) {
return obj instanceof Tok
    && this.rang() == ((Tok) obj).rang;
}

Comparing two objects can be as null-safe as any other method, the catch here is that a normal method has two parameters but compareTo receives one and the other one is the object itself.

this can NEVER be null, that would mean you're executing code in a null object (no instance). In this case a NullPointerException is going to be thrown right at the invocation of compareTo , making impossible the execution of its code.

There are as many approaches as objects, because a comparation can be based in the fields of a class that CAN be null (intended caps to exclude primitive types). So, long story short, your null checks should cover the object you receive as a parameter in compareTo and the used fields. In addition, if you have an external instance that holds some logic (ie an utilities class) you should check if that instance is also null.

As a side note, whatever you return if any involved object is null must be consistent and documented (you can return -1 or 1, to place nulls at the beginning or at the end). Just avoid returning 0 (that would be the same case as if equals returned true for a null object.

Strange as it may seem but it is not safe. Try to add your Tok to TreeSet or TreeMap (as a key) and you will get a NullPointerException. The problem is that TreeSet imlementation is based on TreeMap. When you will try to add(null) underlying map will try to put your null which will result in NPE

The author is insisting that he does not want to remove null values from his Tok[].
Here is a soultion that allows to sort with NULL values, and is not violating java contracts

To avoid that, you create a compareTo inside class Tok that violates the compareTo contract, you create an explicit NullSafeComparator:

 /**
 * This comparator accepts null objects,
 * sorts ascending, null values are after non null values.
 */
public static final class NullSafeComparator implements Comparator<Tok> {
    public int compare(Tok o1, Tok o2) {
        int r1 = Integer.MAX_VALUE;
        int r2 = Integer.MAX_VALUE;
        if (o1 != null) {
            r1 = o1.rang;
        }
        if (o2 != null) {
            r2 = o2.rang;
        }
        return (r1 < r2 ? -1 : (r1 == r2 ? 0 : 1));
    }
}

simplified class Tok (remove the static keyword its is used to define that all inside one unit test class):

public static class Tok {
    int rang;
    public Tok(int rang) {
        this.rang = rang;
    }
    public String toString() {
        return Integer.toString(rang);
    }
}

Finally a unit test to show:

public void testSort() {

    Tok[] toks = new Tok[5];
    toks[0] = new Tok(3);
    toks[1] = new Tok(1);
    toks[2] = null;
    toks[3] = null;
    toks[4] = new Tok(2);



    Arrays.sort(toks, new NullSafeComparator());



    for (Tok tok: toks) {
        System.out.println(tok);
    }
    assertEquals(1, toks[0]);
    assertNull(toks[4]);
}

Which will give the following desired result:

1
2
3
null
null

According to the documentation :

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.

So if you implement a null-safe method, its behaviour will be unexpected (aka inconsitent with the documentation and propably with the rest of the API).

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