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!!
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:
Byte
,new Byte(String s)
,valueOf(String s)
,decode(String nm)
Short
,new Short(String s)
,valueOf(String s)
,decode(String nm)
Integer
,new Integer(String s)
,valueOf(String s)
,decode(String nm)
Long
,new Long(String s)
,valueOf(String s)
,decode(String nm)
Double
,new Double(String s)
,valueOf(String s)
Float
,new Float(String s)
,valueOf(String s)
BigDecimal
,new BigDecimal(String val)
BigInteger
,new BigInteger(String val)
AtomicInteger
AtomicLong
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());
}
- Using reflection (example), try all known implementations of Number (Double, Integer, etc.) present in your classpath.
- For each one, try the static method valueOf(String) and the String constructor (none are in the
Number
interface). - For each Number instance, make sure you can get a String representation equivalent to the user input. Otherwise you could get integer overflows.
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());
}
}
来源:https://stackoverflow.com/questions/36368235/java-get-valueof-for-generic-subclass-of-java-lang-number-or-primitive