I am creating a Matrix
class in Java to use in a linear algebra program. Right now it holds doubles, but I want to abstract it.
I've created an interface called MatrixElement
, which will contain add
, multiply
, divide
, and any other arithmetic methods needed to perform these matrix operations.
Here is a snippet of the Matrix
class:
public class Matrix<T extends MatrixElement> {
private ArrayList<ArrayList<T>> rows;
public Matrix(int numRows, int numCols) {
if (numRows <= 0) {
throw new IllegalArgumentException("Matrix must have at least one row.");
}
if (numCols <= 0) {
throw new IllegalArgumentException("Matrix must have at least one column.");
}
this.rows = new ArrayList<ArrayList<T>>();
for (int r = 0; r < numRows; r++) {
ArrayList<T> row = new ArrayList<T>();
for (int c = 0; c < numCols; c++) {
row.add(new T());
}
this.rows.add(row);
}
}
/* lots of methods omitted */
public static Matrix sum(Matrix a, Matrix b) {
if (a.numRows() != b.numRows() || a.numCols() != b.numCols()) {
throw new IllegalArgumentException("Matrices must be the same size.");
}
Matrix sum = new Matrix(a.numRows(), b.numCols());
for (int r = 1; r <= b.numRows(); r++) {
for (int c = 1; c <= b.numCols(); c++) {
sum.setEntry(r, c, a.getEntry(r, c).add(b.getEntry(r, c)));
}
}
return sum;
}
public Matrix add(Matrix matrix) {
return Matrix.sum(this, matrix);
}
}
Here is how methods are declared in MatrixElement
public interface MatrixElement {
public MatrixElement add(MatrixElement e);
}
Finally, here's a sample class I've created that implements this interface:
public class Fraction implements MatrixElement {
private BigInteger numerator;
private BigInteger denominator;
public Fraction(int num, int denom) {
numerator = BigInteger.valueOf(num);
denominator = BigInteger.valueOf(denom);
}
@Override
public Fraction add(MatrixElement e) {
Fraction f;
try {
f = (Fraction) e;
} catch (ClassCastException cce) {
throw new IllegalMatrixOperationException("Cannot add " + this.getClass().toString() + " and " + e.getClass().toString());
}
/* addition code omitted */
return this;
}
}
The main idea here is this:
Matrix
objects can hold instances of any one class that implements the interface MatrixElement
MatrixElement
contains arithmetic methods necessary for matrix manipulation, such as add
MatrixElement
can only use its methods on other instances of the same class. For example, Fraction
can only be added to other Fraction
instances. Two classes may implement MatrixElement
, but they shouldn't necessarily be able to add to one another.I ran this design by another programmer and was told that casting like this is bad practice. If so, what is the correct way to do this? How can I use an interface to 'group' classes that have similar functionality to be used in parameterization, but then restrict which children of said interface can be used in the children's methods?
There are two requirements for generics in the post above.
Requirement 1: Elements that can be added to other elements of the same type
You would like to impose a contract on certain data types so that instances of those types can be added to other instances of the same type, and same type only. The intention is to support the following kinds of operations:
int + int = int
long + long = long
real fraction + real fraction = real fraction
complex number + complex number = complex number
This contract can be expressed as follows:
public interface MatrixElement<T> {
T add(T e);
}
In simple language, this contract says that a MatrixElement
is a type whose instances can be added to other instances of the same type to yield yet another instance of the same type, which is what was required.
Requirement 2: Two-dimensional matrices of elements, all of the same type can be added to other two-dimensional matrices of elements of the same type and same dimensions
This contract can be expressed as:
public class Matrix<T extends MatrixElement<T>> {
public Matrix<T> add (Matrix<T> another) { ... }
}
This contract says that a Matrix
consists of elements of type MatrixElement
, such that all elements are of the same type. The Matrix
also allows addition with another Matrix
, provided both have elements of the same type. The result is returned as a third Matrix
with elements of the same type.
A sample is available on Github.