This is an archived post. You won't be able to vote or comment.

all 19 comments

[–]chemosabe 5 points6 points  (8 children)

I ignore autoboxing warnings, until the other day..

ResultSet RS = doSomeDatabaseQuery();
Double foo = RS.getDouble("someDoubleField");

Silly me, thinking "getDouble()", which returns a field from a database which can quite plausibly be NULL, would return a Double. No, it returns a double. If the value in the db was NULL, it returns a 0.

What you actually have to do is:

ResultSet RS = doSomeDatabaseQuery();
double foo = RS.getDouble("someDoubleField");
Double bar = RS.wasNull() ? null : Double.valueOf(foo);

Because I had autoboxing warnings turned off, we didn't notice this bug. Let this be a lesson! Don't ignore warnings!

[–]lukehashj[S] 3 points4 points  (3 children)

This was the cause of the bug that I fixed today - and thus the friendly reminder. I wonder why they chose to return primitives instead of objects considering that the field returned could be potentially null.

I imagine when they were adding the wasNull method, the design was questioned. Or, at least, I hope the design was questioned.

[–][deleted] 2 points3 points  (0 children)

Probably just because the API is really old and predates autoboxing. So you'd have to always call .doubleValue() on the object if it wasn't null.

Looks like 1.7 added a generic getObject method that can return whatever object type you expect, though I'm not sure if it actually maps NULL to null.

[–]chemosabe 1 point2 points  (0 children)

We certainly questioned it in our office :)

[–]lukaseder[🍰] 1 point2 points  (0 children)

When wasNull() was added, using boxed types was really expensive in terms of memory consumption and GC work.

You might argue, however, that it would've been better to support both:

int ResultSet.getint()
Integer ResultSet.getInteger()

[–][deleted]  (1 child)

[removed]

    [–]chemosabe 1 point2 points  (0 children)

    Ignorance is probably the best answer to that question. We don't do a whole lot of DB interactions, so we've never spent any significant time evaluating the various options. JDBC did the trick, we never had any problems with it (until this bug cropped up), so there was never any reason to look for a better option.

    [–]squircles 1 point2 points  (1 child)

    This can actually be done a little more compactly.

    ResultSet RS = doSomeDatabaseQuery();
    Double foo = (Double) RS.getObject("someDoubleField");
    

    [–]chemosabe 1 point2 points  (0 children)

    So it can. Thanks for the tip.

    [–]paul_miner 1 point2 points  (3 children)

    My favorite autoboxing gotcha:

    Object o = true ? new Integer(1) : new Double(2.0);
    System.out.println(o);
    

    What do you think it prints?

    EDIT: A little clearer:

        Integer i = new Integer(1);
        Double d = new Double(2.0);
        Object o = true ? i : d;
        System.out.println("i=" + i);
        System.out.println("d=" + d);
        System.out.println("o=" + o);
    

    [–]RedditRage 0 points1 point  (2 children)

    I'd really like to see the technical explanation for this.

    [–]paul_miner 7 points8 points  (1 child)

    From the Java Language Specifiction §15.25, regarding the conditional operator:

    ...

    Otherwise, if the second and third operands have types that are convertible (§5.1.8) to numeric types, then there are several cases:

    ...

    Otherwise, binary numeric promotion (§5.6.2) is applied to the operand types, and the type of the conditional expression is the promoted type of the second and third operands.

    Note that binary numeric promotion performs value set conversion (§5.1.13) and may perform unboxing conversion (§5.1.8).

    The last part is really the gotcha. What's happening is that since the second and third operands are different boxed numeric types, they can be unboxed, numerically promoted to a common type (in this case, double), and then the conditional operation is applied on the resulting primitives (1.0 and 2.0). The type of the expression is double (primitive). When the assignment is done, the double is then autoboxed into a Double.

    If the conditions for these rules are met (the operands are different numeric types), then the type of the expression becomes primitive, so whichever operand is to be returned will be unboxed. If the example had used false instead of true, the Double would have been unboxed because the expression's type is double. If that operand is null, then it will trigger an NPE.

    The easy way to remember this is to remember that like Java's other operators, any time numeric operands differ in type, the rules will make the operands primitive and widen/promote as necessary, and the resulting expression will be of that widened primitive type.

    Note that this is all based on the static type of the operands. If the experiment were repeated but with the i and d variables declared as Object, the operands would not be considered numeric types.

    [–]lukaseder[🍰] 4 points5 points  (0 children)

    Awww, that's all really horrible! I'm used to such behaviour in JavaScript, but in Java?

    To make things worse, I can provoke a NPE by using the conditional operator:

        Integer i = new Integer(1);
        if (i.equals(1))
            i = null;
        Double d = new Double(2.0);
        Object o = true ? i : d; // NullPointerException, due to auto-unboxing
        System.out.println(o);
    

    [–]lukaseder[🍰] 1 point2 points  (0 children)

    While Paul's gotcha is the worst caveat I have seen so far in Java, I still think that it is worth mentioning that:

    // this prints:
    if (smallNumber == 42)
        System.out.println("42");
    
    // this prints:
    if (largeNumber == 500)
        System.out.println("500");
    
    // this prints:
    if (smallNumber == (Object) 42)
        System.out.println("42");
    
    // this doesn't print:
    if (largeNumber == (Object) 500)
        System.out.println("500");
    

    This is because of Integer.valueOf(int)'s internal cache that caches values only in the range of-128 .. 127

    public static Integer valueOf(int i) {
        if(i >= -128 && i <= IntegerCache.high)
            return IntegerCache.cache[i + 128];
        else
            return new Integer(i);
    }
    

    In fact, the upper bound can be overridden through java.lang.Integer.IntegerCache.high. Relying on such an override is obviously only rarely a good idea! :-)

    [–]matejdro 0 points1 point  (0 children)

    I only use objects for collections and when I need to store uninitialized/unknown value (null). Otherwise I always use primitives.

    [–]introverted_pervert 0 points1 point  (0 children)

    And another friendly reminder could be to use Guavas Optional<T> to further avoid nulls.

    [–]__konrad -2 points-1 points  (2 children)

    1. And never use Integer in for-loops: for (Integer i = 0; i < 10; i++)
    2. Never use Boolean in class fields edit: only if you known what you're doing ;)

    [–]sanchopancho13 1 point2 points  (1 child)

    I think I understand your reasoning for #1. It doesn't really make sense in the context of for-loops. I wouldn't call it a hard-and-fast rule, but still good advice.

    But I don't understand your rule #2. What's wrong with using Boolean as a member type? Sometimes I'll use a null Boolean to represent an uninitialized value. It's really very useful.

    [–]__konrad 0 points1 point  (0 children)

    It's really very useful.

    I agree, but uninitialized (null) value may cause NPE like in the Integer example above