I just got into generics with Java, so I set up a little project for myself. I wanted to make a Vector / Point where you could specify the Number
(e.g. Double
, Integer
, Long
, etc).
I ended up getting a decent class object for it, however noticed some issues regarding the methods.
import java.math.BigDecimal;
@SuppressWarnings("WeakerAccess") // Suppresses weaker access warnings
public class Vector<T extends Number> {
private T x;
private T y;
public Vector() {}
public Vector(T x, T y) {
this.x = x;
this.y = y;
}
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
public void dislocate(T offsetX, T offsetY) {
this.setX(addNumbers(getX(), offsetX));
this.setY(addNumbers(getY(), offsetY));
}
public void dislocate(Vector vector) {
this.setX(addNumbers(getX(), vector.getX()));
this.setY(addNumbers(getY(), vector.getY()));
}
@SuppressWarnings("unchecked") // Suppresses cast unchecked warnings
private T addNumbers(Number... numbers) {
BigDecimal bd = new BigDecimal(0);
for(Number number : numbers) {
bd = bd.add(new BigDecimal(number.toString()));
}
return (T) bd;
}
}
The final method, which is the adding numbers method, throws an unchecked cast warning. After I did some research, I figured out it was behaving oddly due to generics, which I'm relatively new in and unable to properly troubleshoot.
What about return (T) bd;
creates the warning? T
has to be an instance of a Number
, so it should be cast-able to a BigDecimal
, right?
So I created my little testing method,
Vector<Double> vec = new Vector<>(1.0, 3.0);
Vector<Double> vec2 = new Vector<>(2.2, 3.9);
vec.dislocate(1.0, 2.7);
System.out.println(vec.getX() + " " + vec.getY());
vec.dislocate(vec2);
System.out.println(vec.getX() + " " + vec.getY());
It works great, printing out 2.0 5.7
and 4.2 9.6
.
The issue then, is when I use a method from Double
, like Double#isNaN()
. It then throws out the ClassCastException, Exception in thread "main" java.lang.ClassCastException: java.base/java.math.BigDecimal cannot be cast to java.base/java.lang.Double
.
This seemed pretty common with other issues people have had with this, however, despite going over the resources, I don't understand why the error is thrown using the Double
methods. The object should be a Double
after the cast, right?
To solve this, you need to provide some means of adding T
s.
For example, a BinaryOperator<T>
is something that takes in two T
s, and returns a T
. So, you can define ones for adding, for example:
BinaryOperator<Double> addDoubles = (a, b) -> a+b;
BinaryOperator<BigDecimal> addBigDecimals = (a, b) -> a.add(b);
Now, you actually need to supply an instance of this to your Vector when you create it, e.g. as a constructor parameter:
public Vector(BinaryOperator<T> adder) {
this.adder = adder; // define a field, too.
}
And now use the BiFunction to add the numbers:
private T addNumbers(T a, T b) {
return adder.apply(a, b); // or you could just invoke this directly.
}
I simplified your addNumbers
always to take two parameters, since you only invoke with two parameters. To do it generically, you'd either need to provide a "generic zero", i.e. a value of type T which is zero for that type, or simply to start from the first element in the varargs array.