Case Study: Generic Matrix Class
Tags: java, programming, learning, beginners
Published: 2024-07-04 20:09:15

This section presents a case study on designing classes for matrix operations using generic types. The addition and multiplication operations for all matrices are similar except that their element types differ. Therefore, you can design a superclass that describes the common operations shared by matrices of all types regardless of their element types, and you can define subclasses tailored to specific types of matrices. This case study gives implementations for two types: **int** and **Rational**. For the **int** type, the wrapper class **Integer** should be used to wrap an **int** value into an object, so that the object is passed in the methods for operations.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8xab4hxbut97p2el9frt.png)

The class diagram is shown in Figure above. The methods **addMatrix** and
**multiplyMatrix** add and multiply two matrices of a generic type **E[][]**. The static method **printResult** displays the matrices, the operator, and their result. The methods **add**, **multiply**, and **zero** are abstract, because their implementations depend on the specific type of the array elements. For example, the **zero()** method returns **0** for the **Integer** type and **0/1** for the **Rational** type. These methods will be implemented in the subclasses in which the matrix element type is specified.

**IntegerMatrix** and **RationalMatrix** are concrete subclasses of **GenericMatrix**. These two classes implement the **add**, **multiply**, and **zero** methods defined in the **GenericMatrix** class.

The code below implements the **GenericMatrix** class. **<E extends Number>** in line 3 specifies that the generic type is a subtype of **Number**. Three abstract methods—**add**, **multiply**, and **zero**—are defined in lines 5, 8, and 11. These methods are abstract because we cannot implement them without knowing the exact type of the elements. The **addMaxtrix** (lines 14–28) and **multiplyMatrix** (lines 31–52) methods implement the methods for adding and multiplying two matrices. All these methods must be nonstatic, because they use generic type **E** for the class. The **printResult** method (lines 55–78) is static because it is not tied to specific instances.

The matrix element type is a generic subtype of **Number**. This enables you to use an object of any subclass of **Number** as long as you can implement the abstract **add**, **multiply**, and **zero** methods in subclasses.

The **addMatrix** and **multiplyMatrix** methods (lines 14–52) are concrete methods. They are ready to use as long as the **add**, **multiply**, and **zero** methods are implemented in the subclasses.

The **addMatrix** and **multiplyMatrix** methods check the bounds of the matrices before performing operations. If the two matrices have incompatible bounds, the program throws an exception (lines 17, 34).

```
package demo;

public abstract class GenericMatrix<E extends Number> {
	/** Abstract method for adding two elements of the matrices */
	protected abstract E add(E o1, E o2);
	
	/** Abstract method for multiplying two elements of the matrices */
	protected abstract E multiply(E o1, E o2);
	
	/** Abstract method for defining zero for the matrix element */
	protected abstract E zero();
	
	/** Add two matrices */
	public E[][] addMatrix(E[][] matrix1, E[][] matrix2) {
		// Check bounds of the two matrices
		if((matrix1.length != matrix2.length) || (matrix1[0].length != matrix2[0].length)) {
			throw new RuntimeException("The matrices do not have the same size");
		}
		E[][] result = (E[][])new Number[matrix1.length][matrix1[0].length];
		
		// Perform addition
		for(int i = 0; i < result.length; i++)
			for(int j = 0; j < result[i].length; j++) {
				result[i][j] = add(matrix1[i][j], matrix2[i][j]);
			}
		
		return result;
	}
	
	/**Multiply two matrices */
	public E[][] multiplyMatrix(E[][] matrix1, E[][] matrix2) {
		// Check bounds
		if(matrix1[0].length != matrix2.length) {
			throw new RuntimeException("The matrices do not have compatible size");
		}
		
		// Create result matrix
		E[][] result = (E[][])new Number[matrix1.length][matrix2[0].length];
		
		// Perform multiplication of two matrices
		for(int i = 0; i < result.length; i++) {
			for(int j = 0; j < result[0].length; j++) {
				result[i][j] = zero();
				
				for(int k = 0; k < matrix1[0].length; k++) {
					result[i][j] = add(result[i][j], multiply(matrix1[i][k], matrix2[k][j]));
				}
			}
		}
		
		return result;
	}
	
	/** Print matrices, the operator, and their operation result */
	public static void printResult(Number[][] m1, Number[][] m2, Number[][] m3, char op) {
		for(int i = 0; i < m1.length; i++) {
			for(int j = 0; j < m1[0].length; j++)
				System.out.print(" " + m1[i][j]);
			
			if(i == m1.length / 2)
				System.out.print(" "+ op + " ");
			else
				System.out.print("     ");
			
			for(int j = 0; j< m2.length; j++)
				System.out.print(" " + m2[i][j]);
			
			if(i == m1.length / 2)
				System.out.print("  =  ");
			else
				System.out.print("     ");
			
			for(int j = 0; j < m3.length; j++)
				System.out.print(m3[i][j] + " ");
			
		System.out.println();
		}
	}
}

```

The code below implements the **IntegerMatrix** class. The class extends
**GenericMatrix<Integer>** in line 3. After the generic instantiation, the **add** method in **GenericMatrix<Integer>** is now **Integer add(Integer o1, Integer o2)**. The **add**, **multiply**, and **zero** methods are implemented for **Integer** objects. These methods are still protected, because they are invoked only by the **addMatrix** and **multiplyMatrix** methods.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kv7nhztqdu20qbu0v497.png)

The code below implements the **RationalMatrix** class. The **Rational** class was introduced in [Rational.java](https://dev.to/paulike/case-study-the-rational-class-1o41). **Rational** is a subtype of **Number**. The **RationalMatrix** class extends **GenericMatrix<Rational>** in line 3. After the generic instantiation, the **add** method in **GenericMatrix<Rational>** is now **Rational add(Rational r1, Rational r2)**. The **add**, **multiply**, and **zero** methods are implemented for **Rational** objects. These methods are still protected, because they are invoked only by the **addMatrix** and **multiplyMatrix** methods.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/my5jz6aynaf35bwgtwur.png)

The code below gives a program that creates two **Integer** matrices (lines 7–8) and an **IntegerMatrix** object (line 11), and adds and multiplies two matrices in lines 14 and 17.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o6prdtj6rejccpstu7yf.png)

The code below gives a program that creates two **Rational** matrices (lines 7–13) and a **RationalMatrix** object (line 16) and adds and multiplies two matrices in lines 19 and 22.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qy472tiq28lijqd2lyj8.png)