0

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:

  • Declare a method with some generic (in a broad sense) Number parameters, possibly more methods with a different number of parameters (e.g. a method with two, a method with three parameters) but still generic in each of them.
  • The parameters must be restricted to some well-known extensions of Number (e.g. Integer, Double).
  • The parameters may come in multiple combinations, potentially any of (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 e.g. 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.

1

1 Answer 1

1

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;
}
Sign up to request clarification or add additional context in comments.

2 Comments

Doesn't work on AtomicInteger, AtomicLong, but works on all other number types
It feels a little too heavy-duty for the use I have in mind, I am afraid. Since in fact I don't need BigInteger and BigDecimal, I think I may resort to interfaces requiring and returning the same type of argument, like double op(double par1, double par2) {...}, after all it's just four methods for float, double, int and long. I think it may be a fair compromise with a template pattern maybe...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.