21.9. (Optional) Case Study: Generic Matrix Class |
This supplement 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 create 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.
The class diagram is shown in Figure 21.7. The methods addMatrix and multiplyMatrix add and multiply two matrices of a generic type E[][] . The static method printResult displays the matrices, the operations, and their result. The methods add , multiply , and zero are abstract methods because their implementations are dependent on the specific type of the array elements. For example, the zero() method returns 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.
Listing 21.11 implements the GenericMatrix class. <E extends Number> in line 1 specifies that the generic type is a subtype of Number . Three abstract methods ” add , multiply , and zero ”are defined in lines 3, 6, and 9. These methods are abstract because they cannot be implemented without knowing the exact type of the elements. The addMaxtrix (lines 12 “30) and multiplyMatrix (lines 33 “56) methods implement the methods for adding and multiplying two matrices. All these methods must be non-static because they use generic type E . The printResult method (lines 59 “83) is static because it is not tied to specific instances.
The matrix element type is generic. This enables you to use an object of any class as long as you can implement the abstract add , multiply , and zero methods in subclasses.
The addMatrix and multiplyMatrix methods (lines 12 “57) 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 16, 36).
1 public abstract class GenericMatrix <E extends Number> { 2 /** Abstract method for adding two elements of the matrices */ 3 protected abstract E add(E o1, E o2); 4 5 /** Abstract method for multiplying two elements of the matrices */ 6 protected abstract E multiply(E o1, E o2); 7 8 /** Abstract method for defining zero for the matrix element */ 9 protected abstract E zero(); 10 11 /** Add two matrices */ 12 public E[][] addMatrix(E[][] matrix1, E[][] matrix2) { 13 // Check bounds of the two matrices 14 if ((matrix1.length != matrix2.length) 15 (matrix1[ ].length != matrix2.length)) { 16 throw new RuntimeException( 17 "The matrices do not have the same size " ); 18 } 19 20 E[][] result = 21 (E[][]) new Number[matrix1.length][matrix1[ ].length]; 22 23 // Perform addition 24 for ( int i = ; i < result.length; i++) 25 for ( int j = ; j < result[i].length; j++) { 26 result[i][j] = add(matrix1[i][j], matrix2[i][j]); 27 } 28 29 return result; 30 } 31 32 /** Multiply two matrices */ 33 public E[][] multiplyMatrix(E[][] matrix1, E[][] matrix2) { 34 // Check bounds 35 if (matrix1[ ].length != matrix2.length) { 36 throw new RuntimeException( 37 "The matrices do not have compatible size" ); 38 } 39 40 // Create result matrix 41 E[][] result = 42 (E[][]) new Number[matrix1.length][matrix2[ ].length]; 43 44 // Perform multiplication of two matrices 45 for ( int i = ; i < result.length; i++) { 46 for ( int j = ; j < result[ ].length; j++) { 47 result[i][j] = zero(); 48 49 for ( int k = ; k < matrix1[ ].length; k++) { 50 result[i][j] = add(result[i][j], 51 multiply(matrix1[i][k], matrix2[k][j])); 52 } 53 } 54 } 55 56 return result; 57 } 58 59 /** Print matrices, the operator, and their operation result */ 60 public static void printResult( 61 Number[][] m1, Number[][] m2, Number[][] m3, char op) { 62 for ( int i = ; i < m1.length; i++) { 63 for ( int j = ; j < m1[ ].length; j++) 64 System.out.print( " " + m1[i][j]); 65 66 if (i == m1.length / 2 ) 67 System.out.print( " " + op + " " ); 68 else 69 System.out.print( " " ); 70 71 for ( int j = ; j < m2.length; j++) 72 System.out.print( " " + m2[i][j]); 73 74 if (i == m1.length / 2 ) 75 System.out.print( " = " ); 76 else 77 System.out.print( " " ); 78 79 for ( int j = ; j < m3.length; j++) 80 System.out.print(m3[i][j] + " " ); 81 82 System.out.println(); 83 } 84 } 85 } |
Listing 21.12 implements the IntegerMatrix class. The class extends Generic-Matrix<Integer> in line 1. 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 only invoked by the addMatrix and multiplyMatrix methods.
1 public class IntegerMatrix extends GenericMatrix<Integer> { 2 /** Implement the add method for adding two matrix elements */ 3 protected Integer add(Integer o1, Integer o2) { 4 return new Integer(o1.intValue() + o2.intValue()); 5 } 6 7 /** Implement the multiply method for multiplying two 8 matrix elements */ 9 protected Integer multiply(Integer o1, Integer o2) { 10 return new Integer(o1.intValue() * o2.intValue()); 11 } 12 13 /** Implement the zero method to specify zero for Integer */ 14 protected Integer zero() { 15 return new Integer( ); 16 } 17 } |
Listing 21.13 implements the RationalMatrix class. The Rational class was introduced in §11.5, "Case Study: The Rational Class." Rational is a subtype of Number . The RationalMatrix class extends GenericMatrix<Rational> in line 1. After the generic instantiation, the add method in GenericMatrix<Rational> is now Rational add(Rational o1, Rational o2) . The add , multiply , and zero methods are implemented for Rational objects. These methods are still protected, because they are only invoked by the addMatrix and multiplyMatrix methods.
1 public class RationalMatrix extends GenericMatrix<Rational> { 2 /** Implement the add method for adding two rational elements */ 3 protected Rational add(Rational r1, Rational r2) { 4 return r1.add(r2); 5 } 6 7 /** Implement the multiply method for multiplying 8 two rational elements */ 9 protected Rational multiply(Rational r1, Rational r2) { 10 return r1.multiply(r2); 11 } 12 13 /** Implement the zero method to specify zero for Rational */ 14 protected Rational zero() { 15 return new Rational( , 1 ); 16 } 17 } |
Listing 21.14 gives a program that creates two Integer matrices (lines 4 “5) and an IntegerMatrix object (line 8), and adds and multiplies two matrices in lines 12 and 16. The output is shown in Figure 21.8.
1 public class TestIntegerMatrix { 2 public static void main(String[] args) { 3 // Create Integer arrays m1, m2 4 Integer[][] m1 = new Integer[][]{{ 1 , 2 , 3 }, { 4 , 5 , 6 }, { 1 , 1 , 1 }}; 5 Integer[][] m2 = new Integer[][]{{ 1 , 1 , 1 }, { 2 , 2 , 2 }, { , , }}; 6 7 // Create an instance of IntegerMatrix 8 IntegerMatrix integerMatrix = new IntegerMatrix(); 9 10 System.out.println( "\nm1 + m2 is " ); 11 integerMatrix.printResult( 12 m1, m2, integerMatrix.addMatrix(m1, m2) , '+' ); 13 14 System.out.println( "\nm1 * m2 is " ); 15 integerMatrix.printResult( 16 m1, m2, integerMatrix.multiplyMatrix(m1, m2) , '*' ); 17 } 18 } |
Listing 21.15 gives a program that creates two Rational matrices (lines 4 “10) and a RationalMatrix object (line 13), and adds and multiplies two matrices in lines 17 and 21. The output is shown in Figure 21.9.
1 public class TestRationalMatrix { 2 public static void main(String[] args) { 3 // Create two Rational arrays m1 and m2 4 Rational[][] m1 = new Rational[ 3 ][ 3 ]; 5 Rational[][] m2 = new Rational[ 3 ][ 3 ]; 6 for ( int i = ; i < m1.length; i++) 7 for ( int j = ; j < m1[ ].length; j++) { 8 m1[i][j] = new Rational(i + 1 , j + 5 ); 9 m2[i][j] = new Rational(i + 1 , j + 6 ); 10 } 11 12 // Create an instance of RationalMatrix 13 RationalMatrix rationalMatrix = new RationalMatrix(); 14 15 System.out.println( "\nm1 + m2 is " ); 16 rationalMatrix.printResult( 17 m1, m2, rationalMatrix.addMatrix(m1, m2) , '+' ); 18 19 System.out.println( "\nm1 * m2 is " ); 20 rationalMatrix.printResult( 21 m1, m2, rationalMatrix.multiplyMatrix(m1, m2) , '*' ); 22 } 23 } |