I would like to write a method which takes a generic Number
as an argument and returns another Number
, whose type may possibly differ from the first and is passed as a second argument. Something like this:
public <N1 extends Number, N2 extends Number> N2 op(N1 num, Class<N2> retType) {
//Sample operation, where type conflicts arise.
return retType.cast(num + 3);
}
Of course, I can't just cast N1
to N2
if they are, say, Integer
and Long
. However, Number
offers doubleValue()
, intValue()
, ... , methods that could allow me to partially work around the issue with a switch/case statement. That would restrict me to the return types of the xxxValue()
methods the Number
class exposes, thus cutting off AtomicInteger
, AtomicLong
, BigInteger
and BigDecimal
.
This is still acceptable for the application I have in mind, but my methods would not be able to properly handle any custom or future official extensions of the Number
class if not with a default
statement in the switch/case block which should arbitrarily decide which xxxValue()
method to invoke, or throw an exception (which I would like to avoid). I could use an enumeration to encapsulate parameters by type, but I am afraid my code would get too convoluted and tricky to use.
The answer to this question gives further insight as to declaring a single generic type for two parameters (it doesn't enforce that both parameters will be, in fact, of the same type at runtime), which sure is worth mentioning here.
What I want to achieve is:
Number
parameters, possibly more methods with a different number of parameters (eg a method with two, a method with three parameters) but still generic in each of them. Number
(eg Integer
, Double
). (Double, Integer)
, (Double, Double)
, (Integer, Double)
, (Integer, Integer)
. I would like not to define multiple methods with different signatures. Even while fixing the return type eg to Double
, the number of methods would explode as more arguments and types are added.
Of course, I may always resort to specific, ad-hoc implementations for each method, as I am probably not going to need every possible combination of types. I would still like my design to be flexible enough while enforcing these type constraints.
Here's my best workaround, which calls the constructor that accepts a String as the parameter, and returns a null if it fails (Edited to remove printStackTrace and remove one unnecessary branch)
public static <R extends Number> R op(Number num, Class<R> retType) {
BigDecimal three = BigDecimal.valueOf(3);
BigDecimal bdNum = new BigDecimal(num.toString());
//add three
BigDecimal bdResult = bdNum.add(three);
String strResult = bdResult.toString();
Constructor[] cons = retType.getDeclaredConstructors();
for (Constructor con: cons) {
if (con.getParameterCount() == 1) {
if (con.getGenericParameterTypes()[0] == String.class) {
try {
return (R)con.newInstance(strResult);
} catch (InstantiationException | IllegalAccessException | NumberFormatException e) {
} catch (InvocationTargetException e) {
//if here then either the decimal place is causing a problem
// when converting to integral type,
// or the value is too large for the target type
// so let's try to remove the decimal point by truncating it.
strResult = bdResult.toBigInteger().toString();
try {
return (R)con.newInstance(strResult);
} catch (NumberFormatException | IllegalAccessException | InstantiationException | InvocationTargetException e1) {
}
}
//if here, then the most likely the integral type is too large
//like trying to put 3,000,000,000 into an int
// when largest int possible is 2,147,483,647
}
//if here, then no constructors with 1 String parameter
}
}
return null;
}
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.