5

After reading through a lot of questions, I asked myself if it is possible to solve the dilemma of transforming a string into a generic number WITHOUT using a hardcoded approach.

For example: I get from a method a parameter with the type Class With Number.isAssignableFrom or other ways I can check, if this is a number class. But I also get from the user an input. As a string.

The question is: Can I now somehow transform this string into the requested number object without building an if statement for every case?

Example code, properly not working:

Object ret = null;

for(int i=0;i<method.getParameterTypes().length; i++ ) {
    Class<?> param = method.getParameterTypes()[i];
    String argString = getUserInput(_in, "Argument ("+param.getSimpleName()+"): ");

    if( Number.isAssignableFrom(param) )
        ret = ((Class<NumberChild>)param).valueOf(argString);
    /// OR
        ret = param.cast( Double.valueOf(argString) )
}

The even advance this question: Could it be possible to cast every primitive in something similar in from the above way?

Note: The approach here should completely focus on a non hardcoded solution. My currently running code uses the approach of hardcoding every single case. But in those cases a more general solution would be much more interesting.

EDIT: I'm sorry for misunderstandings, but I mean with an hardcoded approach, an approach that tests through every possible case like:

 if( integer ); do ...
 if( double ); do ...
 if( long ); do ...

But that is exactly, what I want to work around. To clarify: This is just a challenge. My life or code is not depending on it, I just want to know IF it is possible!!

Georg Friedrich
  • 183
  • 2
  • 12
  • 1
    So, how would you know whether it should be cast to, say, a `Double` or a `Long`? Or worse, a `Integer` vs a `Long`? – Austin Apr 02 '16 at 01:57
  • That is a really interesting question. And the reason why it is there above. If I was unclear: Sorry. I have my `Class> param`. That can be class double, int, ... (Actually, I do the test in my production code so far). My final output is actually just an `Object[]` that I use as an args input for `method.invoke(...)`. Question now is: How do I do the cast, without ever directly asking which actually precise class it is. I will do a test, if the `param` is a number or not, yes. But I don't want to explicitly test for lets say `int.class.equals( param )`. I can let my code use only primitives – Georg Friedrich Apr 02 '16 at 02:04
  • Can you write the signature of the method you would like to have? For instance ` getNumber(String input, Class outputClass)` or just `Number getNumber(String)` ? – pyb Apr 02 '16 at 02:25
  • I don't exactly understand what you mean. But I would say yes. (I believe that I did the same cast [If I understand it correctly] to decode my enums) – Georg Friedrich Apr 02 '16 at 02:31

3 Answers3

5

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());
}
Andreas
  • 154,647
  • 11
  • 152
  • 247
  • That is a really good one. But doesn't support primitives. :D You could help me: If I use in my invoke arguments for example Integer, even if it is requesting an int. Does it still work? (As in normal code, that cast is done automatically) – Georg Friedrich Apr 02 '16 at 02:24
1

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());
    }   
}
Community
  • 1
  • 1
Austin
  • 8,018
  • 2
  • 31
  • 37
  • Wow. The idea is understandable. But where I'm dying is the to understand the concept of the boxing convention. Why do they cache only the values -128 to 127. IF I understand it somehow, it is used to convert Integer to int and vice versa. But my range of int or Integer exceeds the limit to much as that I could use the cache in any way. But, whatsoever. I will research this topic. – Georg Friedrich Apr 02 '16 at 03:05
  • Since primitives are not objects, they have to be handled differently, e.g. you can't store an `int` in a `Collection`. Without autoboxing, you'd have to manually box the primitive. Regarding the caches, they eliminate the need to instantiate multiple copies of commonly-used values (granted, "commonly-used" is a relative term), which saves memory. If your values exceed the -128 to 127 range and you don't change the `AutoBoxCacheMax`, then the static factories simply create a new instance. – Austin Apr 02 '16 at 03:19
  • That's what I thought. But that would mean, that it makes no difference if you store a value within the range between -128 to 127. But in the moment you go over that you request more memory. That concept is then completely different from every c approach I heard so far. :D – Georg Friedrich Apr 02 '16 at 03:22
0
  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.
pyb
  • 4,813
  • 2
  • 27
  • 45
  • That is actually the thing, that I want to work around. I never want to specifically test for every case, but want to have a more general approach that only tests for Enum, Number, String, ... – Georg Friedrich Apr 02 '16 at 02:17
  • To clarify, in step 1 I was suggesting to use a loop on the implementations of Number, using Google Reflections [`Reflections#getSubTypesOf()`](http://reflections.googlecode.com/svn/trunk/reflections/javadoc/apidocs/org/reflections/Reflections.html#getSubTypesOf%28java.lang.Class%29) for instance. – pyb Apr 02 '16 at 02:22
  • Okay. Sorry misunderstanding. Would be a way to go, but would add an extra loop to the code. Can mean in Java a lot. – Georg Friedrich Apr 02 '16 at 02:27
  • You should avoid thinking about [premature optimization](http://c2.com/cgi/wiki?PrematureOptimization). – pyb Apr 02 '16 at 02:33
  • Okay sorry. Just a week ago, I developed a code in Java for Android to put a LUT on a 29,400pixels big array. I also needed to map first through it, to get the min and max raw data in it. So 58,800 iterations per frame (I know, I could do it in JNI, but I wanted to distance myself from it a bit). The image was live with 9fps and essentially every optimisation showed results there. Was thinking about it here, but clearly thats is not one of my problem in this case. – Georg Friedrich Apr 02 '16 at 02:38
  • That said, here's an idea: assuming your Strings are simple (no separator for the thousands, no scientific notation, etc.), you could search for the most memory-efficient class based on the String length and String sorting. For instance, see https://ideone.com/5jfYca Then you could store your results in a https://en.wikipedia.org/wiki/Lookup_table – pyb Apr 02 '16 at 03:17