CS2 Lesson 12: Mutable and Immutable classes

  1. GaussianInteger
  2. Immutability
    1. Mutability vs Immutability
  3. Debugging
  4. Errors
  5. Example: Increment
    1. Debugger
    2. InTheLoop
    3. Exit Ticket

GaussianInteger

Continue working on the GaussianInteger class from last time. Try to implement the multiply method. This should be usable as follows:

GaussianInteger g1 = new GaussianInteger(0, 1);
GaussianInteger g2 = new GaussianInteger(1, 2);
g1.multiply(g2):
System.out.println(g2);

The above should output -2 + 1i, since (0 + 1i)(1 + 2i) = -2 + 1i.

Use this starter code for the exercises we did in class on Thursday. The code for multiply can be tricky. Try to do this yourself first, and then follow along the video above if you need the assistance.

Immutability

The GaussianInteger class we designed above is mutable. That means that GaussianInteger objects can be changed after they are created. This might sounds like a good thing, but it can cause unpredictable behavior:

GaussianInteger g = new GaussianInteger(2, 3); // 2 + 3i
SomeLibrary.method(g);
System.out.pritnln(g);

What is output above? We hope that “2 + 3i” will be output, but we can’t be sure: we would need to look closely at what that SomeLibrary method does. But, for example:

String name = "athar";
SomeLibrary.method(name);
System.out.println(name);

I can be absolutely sure that, in the above code, “athar” will be output. That’s because Strings are immutable, so no matter what SomeLibrary does, it cannot possibly change the value of the String name.

How could we re-design the GaussianInteger class to be immutable? First, make all the instance variables final:

private final int real;
private final int imaginary;

Then you will see some compiler errors: those point out where the class actually changes the value of one of those variables.

How do we implement add and multiply in an immutable way? We can’t change this object, so instead return a new GaussianInteger object:

public GaussianInteger add(GaussianInteger other)

Then in your main method, you would call the add and multiply methods as follows:

GaussianInteger one = new GaussianInteger(1, 0);
GaussianInteger i = new GaussianInteger(0, 1);

System.out.println(one); // should be 1 + 0i
System.out.println(i); // should be 0 + 1i
System.out.println(one.add(i)); // should be 1 + 1i
System.out.println(one); // should still be 1 + 0i
System.out.println(i.multiply(i)); // should be -1 + 0i
System.out.println(i); // should be 0 + 1i

Try making these changes on your own, and then follow along the video below as I talk through modifying the GaussianInteger class to be immutable.

Mutability vs Immutability

In the video above, I talk through some of the reasons why one might prefer to make objects mutable or immutable. For example, the benefits of mutable objects include:

The benefits of immutable objects include:

To be fully honest, though, the benefits of immutable objects almost always outweigh the drawbacks. Thread-safety, which we won’t cover, is extremely important as you write code that might need to be parallelized (so it can run on multiple CPUs / cores at once). Some objects must be mutable: an ArrayList, for example, is by definition mutable. But often if we are given a choice, we should try to design our code to be immutable if possible.

Debugging

In the above video, I walk through the steps of using the debugger in IntelliJ. The debugger is a very powerful tool that can help you figure out what’s going wrong with your code when it’s not working.

This tutorial from JetBrains gives a nice walkthrough of using the debugger as well. I strongly encourage you to take advantage of this tool, particularly if you get stuck on some things while working on your projects. On VSCode, we have a similar tool, but it looks a little different. Check here for information about using the VSCode debugger.

Errors

There are a lot of things that can go wrong when we are coding. These can include:

The examples we’ll look at are taken from Bloch and Gafter, Java Puzzlers: Traps, Pitfalls and Corner Cases (2005). Exercises for today are here.

Example: Increment

int j = 0;
for (int i = 0; i < 100; i++) {
  j = j++;
}
System.out.println(j);

Exercise:

Debugger

Since I already introduce the debugger tool, we might as well use it here to help us figure out what’s going on.

InTheLoop

Look at the InTheLoop program:

public static final int END = Integer.MAX_VALUE;
public static final int START = END - 5;

public static void main(String[] args) {
    int count = 0;
    for (int i = START; i <= END; i++)
        count++;
    System.out.println(count);
}

Exit Ticket

On BrightSpace, before class on Thursday, explain what you think is going on in these two examples. What did you expect to happen, what actually happened, and do you have a guess at what the underlying issue is?

We will go over these on Thursday.