tags:

views:

87

answers:

6

How can I generically create a zero of an arbitrary numeric type?

Here's a toy example: a function that converts a null number into zero.

static <T extends Number> T zeroIfNull(T value) {
    return value == null ? 0 : value;
}

This doesn't compile because the literal zero is of type int, and I need to convert that to type T.

Is it possible to do this at all?

+3  A: 

Is it possible to do this at all?
Not really. For one thing, when value is null, how would method know which Number implementation to return?

Nikita Rybak
Yes, you're right of course. Type erasure bites me again.
Bennett McElwee
@Bennett It's not only about type erasure. If you declare _Number num = null;_ and call _zeroIfNull(num)_, even without type erasure it will be confusing.
Nikita Rybak
If you change the signature slightly static <T extends Number> T zeroIfNull(T value, Class<T> clazz) then you could overcome type erasure. You still have to decide what to do when T is not one of the standard classes. (E.G. Prime models prime numbers 2,3,... and has no zero value. What does zeroIfNull(null,Prime.class) do?)
emory
@Nikita If it weren't for type erasure then that wouldn't be confusing. It would simply be a runtime error.
Bennett McElwee
@emory Your Prime example is very instructive. I have implicitly assumed that all Numbers have a zero value. It's true of the JDK Number hierarchy, but not in general. As for your solution of passing in a Class, it might work, but still, how do I actually create the zero, given a Class object?
Bennett McElwee
A: 

try:

   return value == null ? (T)Integer.valueOf(0) : value;
卢声远 Shengyuan Lu
The only problem is that it always returns zero of integer type. (Also, you can replace "Integer.valueOf(0)" with "0" due to autoboxing)
Nikita Rybak
Integer is kind of Number. It's OK for return <T extends Number>, and since a null(no specific type) is passed in, Integer is OK.
卢声远 Shengyuan Lu
I tried this, and it might even work in my case since the returned value only gets converted to a String with `toString()` anyway. But if I called it with a BigInteger value and then tried to call `abs()` then things would get ugly.
Bennett McElwee
@Shengyuanl yeah, yeah, yeah, but the question was about creating zero instance of "T" type, not Integer type.
Nikita Rybak
A: 

Not that elegant, but I think in most similar case, that's what we can do:

static <T extends Number> T zeroIfNull(T value, Class<T> clazz) {...}

and when used:

BigDecimal v = zeroIfNull(orignalVal, BigDecimal.class);
Adrian Shum
But how do you implement the method?
Bennett McElwee
A: 
import java . math . * ;

class Numbers
{
    public static < T extends Number > T zeroIfNull ( T number , Class<T> clazz ) throws IllegalArgumentException
    {
    if ( clazz == Integer . class )
        {
        return zeroIfNull ( number , clazz , 0 ) ;
        }
    else if ( clazz == Double . class )
        {
        return zeroIfNull ( number , clazz , 0 ) ;
        }
    else if ( clazz == BigInteger . class )
        {
        return zeroIfNull ( number , clazz , BigInteger . ZERO ) ;
        }
    // add a whole bunch more if statements
    throw new IllegalArgumentException ( "Unexpected Number Class " + clazz . getName ( ) + " with possibly undefined zero value." ) ;
    }

    private static < T extends Number > T zeroIfNull ( T number , Class<T> clazz , Object zero )
    {
    if ( number == null )
        {
        return ( clazz . cast ( zero ) ) ;
        }
    else
        {
        return ( number ) ;
        }
    }
}
emory
Hmmm. I guess `Object zero` should be `T zero` in that second method, for clarity at least.
Bennett McElwee
@Bennett Then it would not compile. You would have to add casting in the first method. If you make the second method private and only call it from the first method, then the zero values will be of type T and the cast will not be a problem.
emory
Oh yeah, of course you'll have to cast the zeros. As I said, this would aid clarity.
Bennett McElwee
A: 

The following is an improvement on my first answer.

import java . math . * ;
import java . util . * ;

class Numbers
{
    private static final Map<Class<? extends Number>,Object> zeroes = new HashMap<Class<? extends Number>,Object> ( ) ;

    static
    {
         zeroes . put ( Integer . class , new Integer ( 0 ) ) ;
         zeroes . put ( Double . class , new Double ( 0.0 ) ) ;
         zeroes . put ( BigInteger . class , BigInteger . ZERO ) ;
         // fill it up with all supported classes
    }

    public static < T extends Number > T zeroIfNull ( T number , Class<T> clazz ) throws IllegalArgumentException
    {
    if ( number == null ) // return zero (if we know what zero is)
        {
        if ( zeroes . containsKey ( clazz ) )
            {
            return ( clazz . cast ( zeroes . get ( clazz ) ) ) ;
            }
        throw new IllegalArgumentException ( "Unexpected Number Class " + clazz . getName ( ) + " with undefined zero value." ) ;
        }
    return number ;
    }
}
emory
Yes, this sort of thing is why I asked the question. I was hoping there would be a better way.
Bennett McElwee
+1  A: 

Zero isn't even mentioned in the Number class. If you must do this, and I suggest avoiding nulls, is perhaps:

public static <T> T coalesce(T a, T b) {
    return a==null ? b : a;
}

You could also create a generic interface for handling numbers with features that make sense to your code:

interface NumberOps<T extends Number> {
    T zeroIfNull(T value);
}
Tom Hawtin - tackline
This is the most elegant approach. I wish Java had a coalesce operator. Maybe I should just use C# :)
Bennett McElwee