Reputation: 143
My current situation: I have a superclass Matrix:
public class Matrix {
private final float[] values;
private final int numberOfRows;
private final int numberOfColumns;
public Matrix(int rows, int columns, float... values) {
this.values = new float[numberOfRows * numberOfColumns];
this.numberOfRows = numberOfRows;
this.numberOfColumns = numberOfColumns;
System.arraycopy(values, 0, this.values, 0, values.length);
}
public Matrix scale(int scaleFactor) {
//Some code that returns a scaled version of the matrix
}
}
which is extended by the Vector class that has some vector-specific methods (like e.g. calculating the length of the vector).
Now, let's say I have a vector object named, and I want to scale it by some factor. Then I would call vector.scale(1234);
which results in a matrix object.
However, I would expect the result to be a vector object. I could of course override the scale method in the Vector class and cast the superclass's result to an object of the Vector class, but this doesn't seem like the proper way to do this.
Could anyone give me some pointers on how to implement this so that I can extend the Matrix class and have objects of the subclasses return their own types of objects when they call the scale method?
Upvotes: 2
Views: 2391
Reputation: 328724
If Vector extends Matrix
, you can:
public class Matrix {
public Matrix scale(int scaleFactor) {
... return Matrix ...
}
}
public class Vector extends Matrix {
...
@Override
public Vector scale(int scaleFactor) { // <-- return sub type here!
... return Vector ...
}
}
The rule that overridden methods must have the same type has been weakened in Java 5: You can now use subtypes as well to make overridden methods "more specific."
The advantage of this approach over generics is that it's clean, no (implicit) casting is required and it exactly limits the types without any generics magic.
[EDIT] Now Vector
is just a special kind of Matrix
(the 1-column/row type) which means that the code in scale()
is the same.
Unfortunately, this doesn't work:
public class Vector extends Matrix {
...
@Override
public Vector scale(int scaleFactor) { // <-- return sub type here!
return (Vector) super.scale(scaleFactor);
}
}
since you can't cast Matrix
instances into a Vector
. The solution is to move the scale code into a helper method:
public class Matrix {
protected void doScale(int scaleFactor) {
... original scale() code here...
}
public Matrix scale(int scaleFactor) {
doScale(scaleFactor);
return this;
}
}
public class Vector extends Matrix {
...
@Override
public Vector scale(int scaleFactor) {
doScale(scaleFactor);
return this;
}
}
If the class is immutable (and it probably should be), then you need to use the copy constructor, of course:
public Matrix scale(int scaleFactor) {
Matrix result = new Matrix(this);
result.doScale(scaleFactor);
return result;
}
Upvotes: 2
Reputation: 7387
You're sort of hitting one of the limits of generic types in Java, and I don't see an easy way out without re-defining public Matrix scale(int scaleFactor)
in the subclass and then re-defining the return type: public Vector scale(int scaleFactor)
.
Upvotes: 0
Reputation: 62864
An easy thing you can do is to make the method generic:
public <T extends Matrix> T scale(int scaleFactor) { ... }
When using it, you need to provide the generic type. For example, if you want a Vector
to be returned, you have to do:
Matrix something = ....
Vector result = something.<Vector>scale(...);
Even better option is to make the scale
method abstract
:
public <T extends Matrix> T scale(int scaleFactor);
Then, in all subclasses of Matrix
, you'd be forced to implement the method with a return-type that is a sub-type of Matrix
.
Upvotes: 1
Reputation: 201467
You could make scale
generic. Like
public class Matrix<T> {
// ...
public T scale(int scaleFactor) {
// Some code that returns a scaled version of the matrix
}
}
then your sub-classes could extend Matrix<Vector>
. Note that it would be difficult to practically implement scale
in Matrix
; it might be better to make it abstract
like
public abstract class Matrix<T> {
// ...
public abstract T scale(int scaleFactor);
}
Then your sub-classes must provide an implementation.
Upvotes: 1