Reputation: 43
I'm making some maths classes for a project in Java. I have a Vector3 class, but I will also need Vector4 and Vector2, but obviously I don't want to copy paste my code 3 times.
So what I did is a Vector class that will be the mother class of all vectors. I could just have the Vector class with no child, but I prefer having these child classes because I can add specific things on them, like euler angles operations in Vector3 and also I want to use Vector4 as a mother class for Quaternion. Anyways, here is my simplified Vector class:
public class Vector {
public double[] values;
public Vector(double[] values) {
this.values = values;
}
public int getLength() {
return values.length;
}
public static <T extends Vector> T multiply(T vector, double k) {
double[] values = new double[vector.getLength()];
for(int i = 0; i < values.length; i++)
values[i] = k* vector.values[i];
return new T(values);
}
}
public class Vector3 extends Vector {
public Vector3(double[] values) {
super(values);
}
}
The problem is that the compiler won't let me instantiate a T: "Type parameter T cannot be instantiated directly". But I need this T because I need the returned vector to be the same type as the sent one.
If I do new Vector2(4,2).multiply(42), I need to get a Vector2 and not a Vector. I could also make a multiply method in Vector2 that calls the Vector multiply and then copies the values in a Vector2 but 1. it's awful, 2. that would imply a lot of copy paste between the child vectors, 3. I need performance, so that's not ideal.
I know I can use reflection to solve the problem, but these methods are performance critical so I have to keep it as simple as possible.
I also thought about altering the vector in parameter so I don't have to instantiate a new one, but that's a really bad idea because it can cause weird behavior.
Any help is appreciated.
Upvotes: 3
Views: 312
Reputation: 140534
The easiest way would seem to be to have an instance method on the Vector
class:
Vector make(double[] values) {
return new Vector(values);
}
which you then override in each of the subclasses, making use of a covariant return type:
class Vector3 extends Vector {
//...
@Override Vector3 make(double[] values) {
return new Vector3(values);
}
//...
}
And you can then invoke this in your multiply method.
return vector.make(values);
But honestly, I wouldn't try to encode the length of the vector into the type. What happens when you need a vector with 57032 elements? You surely wouldn't want to create a specific class for that, would you? What happens if you have two different subclasses of Vector
that have the same numbers of elements: are they compatible (e.g. for addition) or not?
Languages which deal with vectors much more naturally (e.g. MATLAB) don't build it into the type; ask yourself if you really need it here.
Upvotes: 3
Reputation: 2276
If it is performance critical you might actually think about having the multiply method altering the state of the vector instead of creating a new one. In my opinion, it is not weird, as long as it is a deterministic and documented behavior.
For an immutable vector class, however, you need to clone
the vector.
public class Vector implements Cloneable {
// not a good idea to make it public, if you don't want any changes here
private double[] values;
public static <T extends Vector> T multiply(T vector, double k) {
Vector temp = vector.clone();
for(int i = 0; i < temp.values.length; i++)
temp.values[i] = k * temp.values[i];
// the clone method guarantees that 'temp' is of type T,
// but since it is not generic, the compiler cannot check it
@SuppressWarnings("unchecked")
T result = (T)temp;
return result;
}
protected Vector clone() {
try {
Vector vector = (Vector)super.clone();
vector.values = Arrays.copyOf(values, values.length);
return vector;
} catch (final CloneNotSupportedException exc) {
// this is a weird design choice of `Object.clone()`, too,
// but back then, we did not have annotations or anything equivalent
throw new AssertionError("we forgot to implement java.lang.Cloneable", exc);
}
}
}
Upvotes: 2