Class 5 — Wednesday, September 9

A reference is an address. Two bugs come out of that — equals that answers no, and a constructor that hands your object away.

Sections 1 and 2 are on your own machine. From section 3 you are in pairs.

  1. Ask your Fraction whether one half is one half — two lines, predict first
  2. What is actually in the variable — draw it, hold it up
  3. Write equals — on the Fraction you already have
  4. The signature that compiles and does nothing
  5. Start A3 — accept, clone, and watch twelve checks fail
  6. Task 1: the constructor that copies — the graded part, after the break

1. Ask your Fraction whether one half is one half

Open your A2 repo. Put these four lines in main:

Fraction a = new Fraction(1, 2);
Fraction b = new Fraction(1, 2);
System.out.println(a == b);
System.out.println(a.equals(b));

Write down both predictions before you run it. Two lines, two answers, true or false.

Run it. Then note both answers down — we are going to come back to them.

Line 3 prints false for everybody. == on two objects asks are these the same object, and they are not — you wrote new twice.

Line 4 prints false too — for everybody in this room.

You inherited Object’s equals, and Object’s equals is ==. So the one method whose entire job is answering are these the same value gives the same answer as the operator that does not even ask the question. It is wrong for every one of you, and nothing warned you.

That is the lesson. Not a bug you wrote — a default you were given.

2. What is actually in the variable

On paper. Draw a box for a and a box for b, and draw what is inside each one. Then add this line and run it:

Fraction c = a;
System.out.println(a == c);

Draw c too.

Done looks like: a drawing where a and c do not each hold their own copy of 1/2, and you can say in one sentence what they hold instead. Hold it up.

3. Write equals

Pair up now: one machine, and whoever typed least last time drives. You swap at the break.

If you are on Zoom

The two of you are a pair. I will put you in a breakout room at the start of each of these blocks and close it when we come back together.

Whoever types shares the screen and the other person navigates.

Write equals on Fraction. Use exactly this signature:

@Override
public boolean equals(Object other) {

Two fractions are equal when the numerators match and the denominators match.

You will need three steps in the body: find out whether other is a Fraction at all, get at its fields, then compare them. instanceof does the first two at once:

if (!(other instanceof Fraction that)) {
    return false;
}

After that line, that is a Fraction and you can read that.num.

Done looks like: section 1’s line 4 printing true, and a.equals("1/2") printing false rather than crashing.

System.out.println(new Fraction(1, 2).equals(new Fraction(2, 4)));

true — and you did not write a line of code to make that work.

It works because of the invariant you built in A2: a Fraction is always stored in lowest terms, so new Fraction(2, 4) already has num == 1 and den == 2 before equals ever sees it. There is only one way to store one half, so comparing the fields is the same as comparing the values.

Take the reduction out of the constructor and this equals starts answering false to a question every human answers true. That is what an invariant buys you: it moves work out of every method that would otherwise have to cope.

4. The signature that compiles and does nothing

Change your method to take a Fraction instead of an Object, and delete the @Override:

public boolean equals(Fraction other) {

It compiles. Section 1 still prints true. Everything looks fine.

Now hand the question to somebody else and ask it again:

List<Fraction> half = List.of(new Fraction(1, 2));
System.out.println(half.contains(new Fraction(1, 2)));

false. The list is holding one half and it says it does not contain one half.

Now put the @Override back.

It will not compile: method does not override or implement a method from a supertype. That is the whole point. There is nothing in Object called equals(Fraction), so you replaced nothing — you added a second method with a similar name, sitting beside the inherited one.

Your own call finds yours, because your code says Fraction on both sides and Java picks the match at compile time. The list has a List<Fraction> but its contains was written long before your class existed, so it can only call Object’s — and Object’s is ==.

Put it back to equals(Object) before section 5.

@Override is the only thing that caught this. Without it the mistake is silent, and it stays silent until something else calls equals on your behalf.

5. Start A3

Accept the A3 invite from Discord and clone it. Both partners accept — you each need somewhere to push.

Run the test before you change anything:

javac -d out src/*.java
java -ea -cp out MatrixTest

In IntelliJ, -ea goes in the run configuration’s VM options.

A2’s test checked your constructor and add, and nothing else — which is why a few of you have a multiply that still returns null and a green test run. This one checks twelve things.

Done looks like: a failure naming arrayConstructorCopiesTheValuesIn. That is not a problem to fix in the test — it is the assignment telling you which method to write first.

If it passes, assertions are off

The test says so itself and throws. If you see neither a failure nor that message, you are not running what you think you are running.

6. Task 1: the constructor that copies

Write Matrix(double[][] values). This is task 1 of A3 and it is graded — we start it here so that everybody has the idea, and you finish it on your own machine.

The row count is values.length and the column count is the length of the first row. Assume a rectangle.

The one thing not to do is this.cells = values. It compiles. Section 2 is the reason it is wrong, and the test has a check that catches it:

double[][] source = {{1, 2}, {3, 4}};
Matrix m = new Matrix(source);
source[0][0] = 99.0;

Done looks like: the first three checks passing, so the failure moves down to copyConstructorMakesASeparateObject. Then commit and push — both partners — before you leave.

Sharing a machine means the passenger still has to get the file onto their own repository, and Discord is the usual way. On Zoom you already have a machine each, so you each push your own and skip that step.

cells is final, and it still did not protect anything.

final on a field means this field will never point at a different object. It says nothing about the object it already points at. The array is still an array, and anyone holding a second reference to it can write to it whenever they like.

Same fact as section 2, one level down: this.cells = values gives your matrix and the caller two names for one array.