Java get valueOf for generic subclass of java.lang.Number or primitive

若如初见. 提交于 2019-12-04 11:44:08

UPDATE

Since the method described in the original answer (see below) doesn't support primitives, and can only support classes which has a constructor with a single String parameter, it would be better to explicitly specify the parser method to use, for each class.

With Java 8 method references, this becomes much easier.

As you can see, even primitive values can be handled, with appropriate parse method, however the parse() method here still returns Object, so any primitive value is still boxed. That is usually the case when handling primitives through reflection.

This is a reduced example. See IDEONE for full working example.

private static HashMap<Class<?>, Function<String,?>> parser = new HashMap<>();
static {
    parser.put(boolean.class   , Boolean::parseBoolean); // Support boolean literals too
    parser.put(int.class       , Integer::parseInt);
    parser.put(long.class      , Long::parseLong);
    parser.put(Boolean.class   , Boolean::valueOf);
    parser.put(Integer.class   , Integer::valueOf);
    parser.put(Long.class      , Long::valueOf);
    parser.put(Double.class    , Double::valueOf);
    parser.put(Float.class     , Float::valueOf);
    parser.put(String.class    , String::valueOf);  // Handle String without special test
    parser.put(BigDecimal.class, BigDecimal::new);
    parser.put(BigInteger.class, BigInteger::new);
    parser.put(LocalDate.class , LocalDate::parse); // Java 8 time API
}

@SuppressWarnings({ "rawtypes", "unchecked" })
private static Object parse(String argString, Class param) {
    Function<String,?> func = parser.get(param);
    if (func != null)
        return func.apply(argString);
    if (param.isEnum()) // Special handling for enums
        return Enum.valueOf(param, argString);
    throw new UnsupportedOperationException("Cannot parse string to " + param.getName());
}

ORIGINAL ANSWER

Javadoc for Number (Java 7) lists the following "direct known subclasses", with the shown methods for parsing a single String argument:

As you can see, you'd be best off using a constructor with a String parameter. That way would will get support for BigDecimal and BigInteger too.

Now, as for how. Use reflection. You have the Class, so ask it for the constructor, and invoke it.

Class param = /*code here*/;
String argString = /*code here*/;

Object ret;
try {
    Constructor ctor = param.getConstructor(String.class);
    ret = ctor.newInstance(argString);
} catch (ReflectiveOperationException e) {
    throw new UnsupportedOperationException("Cannot convert string to " + param.getName());
}
  1. Using reflection (example), try all known implementations of Number (Double, Integer, etc.) present in your classpath.
  2. For each one, try the static method valueOf(String) and the String constructor (none are in the Number interface).
  3. For each Number instance, make sure you can get a String representation equivalent to the user input. Otherwise you could get integer overflows.
Austin D

This answer extends the one provided by @Andreas to take advantage of the static caches used by Integer, Short, and Byte (see this answer for details). This is possible because of the static factory method valueOf(String) provided by each of these classes. For example, by default, Integer caches all values between -128 and 127 (and this range can be extended using the -XX:AutoBoxCacheMax JVM option).

public static Number asNumber(String str,
        Class<? extends Number> param) throws UnsupportedOperationException {
    try {
        /*
         * Try to access the staticFactory method for: 
         * Byte, Short, Integer, Long, Double, and Float
         */
        Method m = param.getMethod("valueOf", String.class);
        Object o = m.invoke(param, str);
        return param.cast(o);
    } catch (NoSuchMethodException e1) {
        /* Try to access the constructor for BigDecimal or BigInteger*/
        try {
            Constructor<? extends Number> ctor = param
                    .getConstructor(String.class);
            return ctor.newInstance(str);
        } catch (ReflectiveOperationException e2) {
            /* AtomicInteger and AtomicLong not supported */
            throw new UnsupportedOperationException(
                    "Cannot convert string to " + param.getName());
        }
    } catch (ReflectiveOperationException e2) {
        throw new UnsupportedOperationException("Cannot convert string to "
                + param.getName());
    }   
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!