Formatting using DecimalFormat throws exception - "Cannot format given Object as a Number"

Arun Sudhakaran picture Arun Sudhakaran · Jul 24, 2017 · Viewed 32k times · Source

This might look like a repeated question but I tried in all the below links and can't get a proper answer.

Cannot format given Object as a Number ComboBox

Illegal Argument Exception

But I'm not getting what's wrong. Here is my code

DecimalFormat twoDForm = new DecimalFormat("#.##");
double externalmark = 1.86;
double internalmark = 4.0;
System.out.println(String.valueOf((externalmark*3+internalmark*1)/4));
String val = String.valueOf((externalmark*3+internalmark*1)/4);
String wgpa1=twoDForm.format(val); // gives exception
String wgpa2=twoDForm.format((externalmark*3+internalmark*1)/4)); // works fine
System.out.println(wgpa1);

The format method takes Object type argument, so that's why I passed a String object which gives exception

Exception in thread "main" java.lang.IllegalArgumentException: Cannot format given Object as a Number.

But when I give double value as argument the program works fine. But if the method is defined with Object type argument why I'm getting an exception while passing a String and not getting exception while passing double?

Answer

davidxxx picture davidxxx · Jul 24, 2017

The format() method of DecimalFormat is overloaded.

In the working case, you are invoking :

 public final String format(double number)

And in the failing case, you are invoking :

 public final String format (Object obj) 

The first method takes a very specific argument. It expects a double.

This is not the case of the second one, which the type accepted is very broad : Object and where so the check on the type passed is performed at runtime.

By providing a argument that is not a double but a String, the method invoked is the second one.

Under the hood, this method relies on the format(Object number, StringBuffer toAppendTo, FieldPosition pos) method that expects to a number argument that is an instance of the Number class (Short, Long, ... Double):

@Override
public final StringBuffer format(Object number,
                                 StringBuffer toAppendTo,
                                 FieldPosition pos) {
    if (number instanceof Long || 
        number instanceof Integer ||                   
        number instanceof Short || 
        number instanceof Byte ||                   
        number instanceof AtomicInteger ||
        number instanceof AtomicLong ||
        (number instanceof BigInteger && ((BigInteger)number).bitLength () < 64)) {

        return format(((Number)number).longValue(), toAppendTo, pos);
    } else if (number instanceof BigDecimal) {
        return format((BigDecimal)number, toAppendTo, pos);
    } else if (number instanceof BigInteger) {
        return format((BigInteger)number, toAppendTo, pos);
    } else if (number instanceof Number) {
        return format(((Number)number).doubleValue(), toAppendTo, pos);
    } else {
        throw new IllegalArgumentException("Cannot format given Object as a Number");
    }
}

But it is not the case as you passed to it a String instance.

To solve the problem, either pass a double primitive as in the success case or convert your String into an instance of Number such as Double with Double.valueOf(yourString).
I advise the first way (passing a double) as it is more natural in your code that already uses double primitives.
The second one requires a additional conversion operation from String to Double.