0

I've got a little but annoying problem with use of Generics in a Function.

The Function has to convert into a Double any value that could be BigDecimal or BigInteger. That's why I designed it with a <T> type for incoming argument.

The problem is that when I'm using it, I have to cast the given argument with <T>...

Here is the code of the Function:

private Function<T, Double> bigToDouble = value -> {
    BigDecimal bigDec = null;
    if (value instanceof BigInteger) {
        BigInteger bigInt = (BigInteger) value;
        bigDec = new BigDecimal(bigInt);
    }
    if (value instanceof BigDecimal) {
        bigDec = (BigDecimal) value;
    }
    return NumberUtils.toDouble(bigDec, NumberUtils.DOUBLE_ZERO);
};

When I test it, I've got an error if I do not cast the given argument with <T> :

BigDecimal bigDec = new BigDecimal("2.5");      
BigInteger bigInt = new BigInteger("150000");
System.out.println("FUNCTION TEST = " + bigToDouble.apply((T) bigInt));
System.out.println("FUNCTION TEST = " + bigToDouble.apply((T) bigDec));

What I expect is to call it this way, simply:

bigToDouble.apply(bigInt)

How should I design it to avoid such behaviour?

6
  • 1
    What does NumberUtils.toDouble(bigDec, NumberUtils.DOUBLE_ZERO) do? How is it different from, say, bigDec.doubleValue()? Why don't you use doubleValue() directly in the first place? Commented Aug 21, 2019 at 7:38
  • 1
    Did you try to use <T extends Number, Double>? Commented Aug 21, 2019 at 7:39
  • Also, you are declaring a field of type Function, not a method. A field can't have a generic parameter like T. Commented Aug 21, 2019 at 7:40
  • @Sweeper a field can have a generic type or parameter if used in a generic class (which we'd have to assume since T has to be declared somewhere). Commented Aug 21, 2019 at 7:42
  • Instead of all that type checking and casting I'd suggest you just provide 2 methods that take one of the types and call NumberUtils.toDouble(bigDec, NumberUtils.DOUBLE_ZERO) (if that's really necessary). That would make it easier to use - just not as a function. Btw, what exactly are you trying to do in the end? How do you intend on using that function? I have a feeling that we're looking at a xy-problem here. Commented Aug 21, 2019 at 7:48

3 Answers 3

4

Did you try using the parent class of BigInteger and BigDecimal, Number?

Instead of using generics, try replacing T with Number, which accepts both BigInteger and BigDecimal. This would look like the following code:

  private Function<Number, Double> bigToDouble = value -> {
    BigDecimal bigDec = null;
    if (value instanceof BigInteger) {
      BigInteger bigInt = (BigInteger) value;
      bigDec = new BigDecimal(bigInt);
    }
    if (value instanceof BigDecimal) {
      bigDec = (BigDecimal) value;
    }
    return NumberUtils.toDouble(bigDec, NumberUtils.DOUBLE_ZERO);
  };


  public void test(){
    BigDecimal bigDec = new BigDecimal("2.5");
    BigInteger bigInt = new BigInteger("150000");
    System.out.println("FUNCTION TEST = " + bigToDouble.apply(bigInt));
    System.out.println("FUNCTION TEST = " + bigToDouble.apply(bigDec));
  }

EDIT

In case you want to use your own <T> type parameter, without having the limitations that you will have when using <T> now, you should declare a generic method. (see: https://docs.oracle.com/javase/tutorial/java/generics/methods.html). The code for that would look like the following then:

  private <T extends Number> Function<T, Double> bigToDouble() {
    return value -> {
      BigDecimal bigDec = null;
      if (value instanceof BigInteger) {
        BigInteger bigInt = (BigInteger) value;
        bigDec = new BigDecimal(bigInt);
      }
      if (value instanceof BigDecimal) {
        bigDec = (BigDecimal) value;
      }
      return NumberUtils.toDouble(bigDec, NumberUtils.DOUBLE_ZERO);
    };
  }

  public void test() {
    BigDecimal bigDec = new BigDecimal("2.5");
    BigInteger bigInt = new BigInteger("150000");
    System.out.println("FUNCTION TEST = " + bigToDouble().apply(bigInt));
    System.out.println("FUNCTION TEST = " + bigToDouble().apply(bigDec));
  }
Sign up to request clarification or add additional context in comments.

2 Comments

Thanx for your answer. You're partially right ^^ Number is a good solution because both BigDecimal & BigInteger extends it. Nevertheless I still have to cast <Number> instead of <T>
If you want to cast <T> then I suggest for you to declare a generic method (I updated the answer with code that will succeed)
1

The reason that you need a cast from BigInteger/BigDecimal to T is that at compile time, the compiler doesn't know what exactly the type T is, so the compiler is not sure if it can cast the BigInteger / BigDecimal to the type T. Thus, you need to do a force cast.

To solve this, one solution is to replace T by the parent class of your parameters classes. Thus the compiler is sure that it can do a downcasting.

Otherwise, you have to tell the compiler explicitly that you know at runtime, when you declare the type T, you are sure that BigInteger or BigDecimal can be cast to this type. Which means that you have to write a force cast every time you invoke the apply method.

Below the code :

public static void main(String[] args) {
        Function<Number, Double> bigToDouble = value -> {
            BigDecimal bigDec = null;
            if (value instanceof BigInteger) {
                BigInteger bigInt = (BigInteger) value;
                bigDec = new BigDecimal(bigInt);
            }
            if (value instanceof BigDecimal) {
                bigDec = (BigDecimal) value;
            }
            return bigDec.doubleValue();
        };

        BigDecimal bigDec = new BigDecimal("2.5");      
        BigInteger bigInt = new BigInteger("150000");
        System.out.println("FUNCTION TEST = " + bigToDouble.apply(bigInt));
        System.out.println("FUNCTION TEST = " + bigToDouble.apply(bigDec));
    }

4 Comments

Hi, you're right, Number is a better solution because BigInteger & BigDecimal extends it, but I still have to cast <Number> instead of <T>
@Lovegiver I've updated the answer with the code. Don't understand why still need a force cast.
@Nechadil you will still need a forecast as right now you are bound to the type parameter defined for <T>. To understand more why: make a class NumberConverter<T extends Number> and instantiate this class twice, once with new NumberConverter<BigDecimal> and new NumberConverter<BigInteger>, here you can see that it is bound to those type (when you apply both types to their righteousness owner), as it's not a type parameter (as used in a generic method).
@Nechadil Thanx, we now have the same code. In my test class, I still have to cast with (Number) whereas the same function doesn't ask me to do so out of the test class. I don't understand why but it works correctly. Use of <Number> type was the key.
1

Would it change if you explicitly declared the bounds for T something like <T extends Number> ?

7 Comments

T extends BigInteger & BigDecimal won't compile because both are classes and in such cases only one class is allowed. The reason is that any T would have to extend both classes and that's just not possible.
Besides that "Would it change ...?" indicates this is more a comment than an answer.
How would your first example work (T extends BigInteger & BigDecimal)? As you cannot extend anything with more than one class (you can only implementing multiple interfaces)? This would result in your compiler failing to compile.
I tried to declare the bounds of <T>, but the Function doesn't accept it. I just can declare <T>, not <T extends Number> for example.
@Lovegiver as you are not declaring a generic method, you cannot introduce your own generic type (see: docs.oracle.com/javase/tutorial/java/generics/methods.html).
|

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.