Wednesday, July 28, 2010

Puzzle 63 - Out of the Box

Java 1.5 introduced Autoboxing and since then a lot of code has been indiscriminately been using Autoboxing. I've picked up a simple case this time around (in fact its based on Joshua Bloch's - Effective Java). We'll look at a more real world problem next week.

package com.test; import java.util.Comparator; public class Order { public static void main(String[] args) { Comparator<Integer> naturalOrder = new Comparator<Integer>(){ public int compare(Integer first, Integer second) { return first < second ? -1 :(first == second ? 0:1); } }; /*This one is obviously broken - outputs 1 instead of 0 - Why?*/ System.out.println(naturalOrder.compare(new Integer(42), new Integer(42))); /*This one works but is still broken - outputs 0 as expected! - Why is this broken?*/ System.out.println(naturalOrder.compare(42,42)); } }

8 comments:

  1. Hmmmm.... in first case, comparison '<' is done between numerical values of these objects and is therefore true, but '==' is done as 'equals()', which also is not true, therefore the first call returns value of '1';

    And the second one.... Well, the compare() function requires two Integer _objects_, therefore 'compare(42,42)' transfers those numerics to objects, and having the same value and being in the same expression, code optimizer bounds both 42s to the same object under the hood, new Integer(42).

    Is this correct?

    Cheers, Vlada

    ReplyDelete
  2. 1: References are diff ..but value is same
    2: References are same(Integer.valueOF(..))

    ReplyDelete
  3. Objects are not primitives. Thats all about it. Btw, glad you are back )

    ReplyDelete
  4. /*This one is obviously broken - outputs 1 instead of 0 - Why?*/
    System.out.println(naturalOrder.compare(new Integer(42), new Integer(42)));

    In this case an instance is created for each number which are not equal when compared with == because they are different references.

    /*This one works but is still broken - outputs 0 as expected! - Why is this broken?*/
    System.out.println(naturalOrder.compare(42,42));

    When autoboxing, Java calls Integer.valueOf( int ) method which looks like this:

    public static Integer valueOf(int i) {
    if (i < -128 || i > 127) {
    return new Integer(i);
    }
    return valueOfCache.CACHE [i+128];
    }

    So for values between -128 and 127 it maintains a cache which is created on the first call (stores instances for all values in the range [-128,127]) and on the second call it returns the same reference. That's why == works.

    But with number outside the range [-128,127] it new instances are created and it will behave the same as in the first case.

    ReplyDelete
  5. "object1 == object2" is different from "object1.equals(object2)"

    ReplyDelete
  6. It's such a bad idea to compare Objects (Integers are Object after all) with a simple "==".

    If you use the new operator you create a new reference, so == won't work.

    And the problem with boxing/unboxing is that we can't really be sure that references will be the same between two converted int.

    More at JLS (in the discussion) :

    http://java.sun.com/docs/books/jls/third_edition/html/conversions.html#190697

    ReplyDelete
  7. It's simple, this line:

    return first < second ? -1 : (first == second ? 0 : 1);

    compares references instead of values. So it returns 0 only when references are the same (point at the same object). Can be fixed eiher with:

    return first.compareTo(second);

    , or:


    return first < second ? -1 : (first.intValue() == second.intValue() ? 0 : 1);

    ReplyDelete
  8. Integers are immutable, therefore == comparisson returns false as Object references are not equal

    ReplyDelete

Solution for this question?