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.

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

  1. Write Matrix(double[][] values). The row count is values.length and 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. final does not stop this. final on cells stops 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.

  2. 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, where that.cells is reachable even though it is private. private is 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.

  1. Write magnitude(). Add up the absolute value of every entry and return the total. Math.abs gives 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.

  2. 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, not Matrix. Write equals(Matrix other) and you have overridden nothing — you have added a second, unrelated method that Java’s collections will never call. The @Override is 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 a Matrix at all. instanceof answers that, and it answers false for null, 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.

  3. 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 HashSet or a HashMap. Those find an object by its hash code first and only then compare with equals. Two equal matrices with different hash codes land in different places, so contains answers no about an object that is sitting right there.

    You are hashing a double[][], and Arrays.hashCode will not do it. That method hashes an array of objects by asking each element for its own hash code, and an element here is a double[] — an object that hashes by address. Two matrices holding identical numbers would get different answers. Look at Arrays.deepHashCode instead, 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 HashSet can 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.

  1. Write add and subtract. 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 DimensionMismatchException whose 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.

  2. 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.

  1. 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.java and there is a check for it in MatrixTest.java, commented out at the bottom of main. Uncomment the call when you write the method.

  2. [Consulting AI allowed] Find two matrices that ought to be equal and are not. Your equals compares doubles with ==, and == on doubles does not mean what school arithmetic says it means. Work out what 0.1 + 0.2 == 0.3 answers before you run it. Then build a pair of matrices where your own add produces something your own equals rejects. Once you have a pair, ask AI how people normally compare doubles and what it would cost to do that here.

  3. [Consulting AI allowed but keep the setup small] Extend MatrixTest.java to cover what it leaves out. Nothing currently checks a 1-by-1 matrix, a matrix with a zero in it, or multiply the 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 in MatrixTest.java itself rather than starting a new file.

  4. 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.