简体   繁体   中英

Java generics and numeric types

I'd like to create a generic method which does effectively this:

class MyClass {

    static <T extends Number> T sloppyParseNumber(String str) {
        try {
            return T.valueOf(str);
        } catch (Exception e) {
            return (T)0;
        }
    }
}

Now above does not compile for two reasons: there's no Number.valueOf() method and 0 can't be cast to T .

Example usage:

String value = "0.00000001";

System.out.println("Double: " + MyClass.<Double>sloppyParseNumber(value));
System.out.println("Float: " + MyClass.<Float>sloppyParseNumber(value));

double d = MyClass.sloppyParseNumber(value);
float f = MyClass.sloppyParseNumber(value);

Is implementing above generic method possible with Java? If yes, how? If no, what's a good alternative approach?


Edit: there seems to be a few possible duplicates, but I did not find one, which covers exactly this. I'm hoping there's some trick to pull, which would allow these two operations: parse string to a Number subclass, and return 0 value for a Number subclass.

Java generics only provide compile time checks and the type information is pretty much thrown away after compilation. So the statement T.valueOf isn't possible in Java. The solution is to go the verbose way as already mentioned in the comments. Also, is there any reason why you want to do MyClass.<Double>sloppyParseNumber(value) but not MyClass.sloppyParseDouble(value) since you are anyway specifying the type at compile time?

I agree 100% with TofuBeer. But in case you wish to avoid verbosity for time sake, this should also do:

static <T extends Number> T sloppyParseNumber(String str,Class<T> clas) {

    if (clas == null) throw new NullPointerException("clas is null");

    try {

        if(clas.equals(Integer.class)) {
            return (T) Integer.valueOf(str);
        }
        else if(clas.equals(Double.class)) {
            return (T) Double.valueOf(str);
        }
        //so on

    catch(NumberFormatException|NullPointerException ex) {
        // force call with valid arguments
        return sloppyParseNumber("0", clas);
    }

    throw new IllegalArgumentException("Invalid clas " + clas);

}

But purely from T , you cannot get the type at runtime.

Static methods are bound by the type, since the type is, at best, Number and Number doesn't have a valueOf method what you are after isn't going to work.

The easiest way is to just make a number of static methods like sloppyParseInt , sloppyParseFloat , etc...

You could do something like this, not sure I like it, and can probably be improved on:

public class Main 
{
    private static final Map<Class<? extends Number>, NumberConverter> CONVERTERS;

    static
    {
        CONVERTERS = new HashMap<>();
        CONVERTERS.put(Integer.class, new IntegerConverter());
    }

    public static void main(String[] args) 
    {
        Number valueA;
        Number valueB;

        valueA = CONVERTERS.get(Integer.class).convert("42");
        valueB = CONVERTERS.get(Integer.class).convert("Hello, World!");

        System.out.println(valueA);
        System.out.println(valueB);
    }
}

interface NumberConverter<T extends Number>
{
    T convert(String str);
}

class IntegerConverter
    implements NumberConverter<Integer>
{
    @Override
    public Integer convert(String str) 
    {
        try
        {
            return Integer.valueOf(str);
        } 
        catch (NumberFormatException ex) 
        {
            return 0;
        }    
    }
}

So, I decided on an alternative approach:

static String trimTo0(String str) {
    if (str == null) return "0";
    str = str.trim();
    if (str.isEmpty()) return "0";
    return str;
}

Usage:

String value = null;
System.out println("Double value: " + Double.parseDouble(trimTo0(value)));

Note that this is more limited than the method in the question, this does not convert invalid, non-numeric strings to "0" . Doing that fully would require two separate methods, one supporting decimal point and another just supporting integers.

You can try this:

private <T> T convertToType(Class<T> clazz,String str) throws Exception {
    return clazz.getConstructor(String.class).newInstance(str);
}

Here you need to consider that the Type must have a constructor with a String parameter.

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