Assignment 3 — Matrix
Build an immutable Matrix. Constructors that copy their input rather than borrow it, an equals with the hashCode that has to come with it, and arithmetic that refuses a shape it cannot handle.
Due Sep 15, 2026
Note: “AI” below means your favorite chatbot, e.g. Google Gemini, Microsoft Copilot, ChatGPT. Where a step does not say otherwise, AI tools are not allowed, as set out in the AI policy in the syllabus.
Start from the starter repo (link on Discord, #announcements). Run the tests before you write anything:
javac -d out src/*.java
java -ea -cp out MatrixTest
-ea is what turns assertions on; without it the test checks nothing, so it throws and says so instead. In IntelliJ, -ea goes in the VM options of the run configuration.
Three files come with it.
src/Matrix.javaholds the fields, the shape constructor, three getters andtoString. Everything below the line in that file is yours.src/DimensionMismatchException.javais written for you. Read it before you writeadd.src/MatrixTest.javais twelve checks, every one of them true of a finishedMatrix. Silence means done. A failure names the next thing to do, and the checks run in the order of the tasks below. This file is yours to change. Marking uses its own copy of these checks, so editing this one — or leaving it half-broken — cannot cost you marks.
A Matrix never changes once it is built. Every field is final, nothing hands out the cells array, and there is no setEntry. add, subtract and multiply each return a new matrix and leave both operands alone. That is the whole design, and several of the tasks below only make sense because of it.
We write task 1 together on Wednesday, in pairs on one machine, so partners will push the same few lines. That is expected. If you were not there, the task says everything you need.
A floor, if you run out of time. Parts 1 and 2 — tasks 1 to 5 — are the ones to get working. They are half the code and all of the ideas: copying rather than sharing, and what it means for two objects to be equal. Part 3 is loops. Push what you have even if it is only the floor. Partial work you pushed beats finished work you did not. Each behaviour is marked on its own, so whatever you finish counts even while the rest is still stubs.
Part 1 — building a matrix
Write
Matrix(double[][] values). The row count isvalues.lengthand the column count is the length of the first row. Assume the caller hands you a rectangle; you do not have to check for a ragged one.Copy the numbers in. Do not write
this.cells = values.That line compiles, and it leaves you holding the caller’s array. The caller still holds it too, and can change it whenever they like — which changes your matrix, from outside, after it was built.
finaldoes not stop this.finaloncellsstops the field being pointed at a different array. It says nothing at all about the numbers inside the array it already points at.The test has a check for exactly this. It builds a matrix from an array, changes the array afterwards, and then asks the matrix what it holds.
Write
Matrix(Matrix that). A new matrix holding the same numbers and sharing nothing with the old one.You have just written a constructor that copies a
double[][], so use it.this(that.cells)on the first line calls it, and nothing else belongs in the body.That line works because you are inside
Matrix, wherethat.cellsis reachable even though it is private.privateis per class, not per object.
Part 2 — asking a matrix questions
Nothing in this part builds a new matrix. Each of the three answers a question about one that already exists, so each needs only the getters the starter already gave you.
Write
magnitude(). Add up the absolute value of every entry and return the total.Math.absgives you the absolute value.It is the shortest method in the assignment and it is the one Monday’s class builds on, so get it working even if you get no further.
Write
equals. Two matrices are equal when they have the same shape and the same numbers in the same places. Use the signature Java expects:@Override public boolean equals(Object other)Object, notMatrix. Writeequals(Matrix other)and you have overridden nothing — you have added a second, unrelated method that Java’s collections will never call. The@Overrideis what turns that mistake into a compile error instead of a silent one.The parameter arrives as an
Object, so the first thing to find out is whether it is aMatrixat all.instanceofanswers that, and it answersfalsefornull, so a separate null check is dead code.A matrix must not equal a string and must not equal a matrix of a different shape. The test checks both.
Write
hashCode. Java’s rule is one line: two objects that are equal must return the same hash code.Nothing makes you follow it. Your code compiles and runs without it, and everything looks fine right up until a matrix goes into a
HashSetor aHashMap. Those find an object by its hash code first and only then compare withequals. Two equal matrices with different hash codes land in different places, socontainsanswers no about an object that is sitting right there.You are hashing a
double[][], andArrays.hashCodewill not do it. That method hashes an array of objects by asking each element for its own hash code, and an element here is adouble[]— an object that hashes by address. Two matrices holding identical numbers would get different answers. Look atArrays.deepHashCodeinstead, and check what it does before you use it. Writing the loop yourself is also fine.The last two checks in the test are this rule: equal matrices agree on their hash code, and a
HashSetcan then find one.
Part 3 — arithmetic
Both of these build a new matrix out of two old ones, and both can be handed a pair they cannot work with. That is what DimensionMismatchException is for.
Read DimensionMismatchException.java before you start. It extends Exception rather than RuntimeException, which makes it checked: the compiler forces every method that can throw it to say so in its signature, and forces every caller to either catch it or say so in turn. The IllegalArgumentException you threw in A2 was unchecked, so no caller was ever made to think about it.
Write
addandsubtract. Each takes another matrix, returns a new one, and changes neither operand.Both need the two shapes to match. When they do not, throw a
DimensionMismatchExceptionwhose message says what the two shapes were. A caller who reads “shapes differ” learns nothing. A caller who reads “2-by-3 and 3-by-2” knows what to fix.The same shape check runs in both methods. Write it once, as a private helper, and call it from both.
Write
multiply. An n-by-m times an m-by-p gives an n-by-p. Entry (i, j) of the result is row i of this matrix run against column j of that one: multiply the pairs and add up the products.The shape rule is not the one from task 6, so the helper you just wrote is the wrong check here. This matrix’s column count has to equal that matrix’s row count. Nothing else has to match, and the two matrices are usually different shapes.
Commit and push. The last push before the deadline is the one that counts.
What else to do (optional part)
Ungraded: no points, and skipping them costs you nothing.
Write
transpose(). Flip the matrix along its main diagonal, so entry (i, j) moves to (j, i) and an n-by-m becomes an m-by-n. There is no shape transpose cannot handle, so it throws nothing and declares nothing.The stub is in
Matrix.javaand there is a check for it inMatrixTest.java, commented out at the bottom ofmain. Uncomment the call when you write the method.[Consulting AI allowed] Find two matrices that ought to be equal and are not. Your
equalscompares doubles with==, and==on doubles does not mean what school arithmetic says it means. Work out what0.1 + 0.2 == 0.3answers before you run it. Then build a pair of matrices where your ownaddproduces something your ownequalsrejects. Once you have a pair, ask AI how people normally compare doubles and what it would cost to do that here.[Consulting AI allowed but keep the setup small] Extend
MatrixTest.javato cover what it leaves out. Nothing currently checks a 1-by-1 matrix, a matrix with a zero in it, ormultiplythe other way round — a 3-by-2 times a 2-by-3 gives a 3-by-3, and the file only ever does it the way that gives a 2-by-2. Follow the shape already in the file: as in A1, automated means the program checks itself on every run and stays quiet unless something breaks. Put them inMatrixTest.javaitself rather than starting a new file.Write one line of Javadoc above each public method — what a caller needs to know, not what the code does. Presentation is 10% of your grade, and it is you explaining this code out loud.