CSCD 211 › lessons

Week 0: value versus reference, built from "why did changing b change a?"

By the end you can say what a variable actually holds, predict when two variables share one object, and choose == or .equals on purpose instead of by guess. Week 1 uses it directly: the rule that compareTo must agree with equals is this same distinction, value versus identity. The diagnostic on June 23 checks it.

The user story behind this. A department chair wants the Schedule they are building to stay exactly as they left it, so that a change made somewhere else cannot quietly reorder or drop a section without their knowing. Whether that guarantee holds comes down to one question: when two variables point at the same list, a change made through one is a change made through both. Week 2 turns this idea into the defensive copy that protects the chair's schedule, and this lesson builds the idea that copy rests on.

Before you start

You should be able to tick each of these. If one is shaky, do the matching cs.jdoner.me CSCD 210 review first, then come back.

The problem (sit with this before reading on)

Here is a short program. Read it, predict what it prints, then read on.

int[] a = {1, 2, 3};
int[] b = a;
b[0] = 99;
System.out.println(a[0]);

You never touched a. You changed b. Yet this prints 99, not 1. Changing b changed a, and nothing in the code says they are connected. This is a classic source of CSCD 210 bugs that "make no sense at first," and the reason is one idea about what a variable holds.

Check yourself. State, in one sentence, the question this surprise forces: what exactly did the line b = a put into b?

Hold that question. Everything below is the answer to it, built one piece at a time.

Atom 1: a variable is a box, and for a primitive the box holds the value

Think of a variable as a labeled box. For a primitive type (int, double, boolean, char), the box holds the value itself, the actual number or character. Nothing else is going on:

int x = 5;
int y = x;   // copy the value 5 into y's box
y = 9;       // change only y's box

After this, x is 5 and y is 9. Assigning y = x copied the number into a separate box, so the two are independent from then on. This is the case that behaves the way most people expect.

Check yourself. With the three primitive variables int n = 7; double r = 1.5; boolean ok = true;, what does each box physically hold?

Atom 2: for an object, the box holds a reference, not the object

Everything that is not a primitive is an object: a String, an array, a Course, anything built with new or with array or string literal syntax. An object does not fit in the box. It lives somewhere else in memory, and the box holds a reference to it: an arrow pointing at where the object lives. new builds the object and hands back the arrow.

int[] xs = new int[3];   // the array lives elsewhere; xs holds an arrow to it
String name = "Ana";     // the String lives elsewhere; name holds an arrow to it

So a primitive box holds a value, and an object box holds an arrow. That one difference is the whole lesson.

Check yourself. Given int n = 7; String name = new String("Ana"); int[] data = new int[3];, which boxes hold a value, and which hold a reference?

Atom 3: assignment copies the box, which explains the opening bug

Assignment always copies what is in the box. For a primitive that copies the value, so you get an independent second value (Atom 1). For an object that copies the arrow, so now two boxes point at the same one object. Two names for one object is called an alias.

That is the opening bug exactly. int[] b = a copied a's arrow into b, so a and b are two arrows to one array. Writing b[0] = 99 reaches through b's arrow to that one array and changes it, and reading a[0] follows a's arrow to the same array and sees the change. No copy of the array was ever made.

Check yourself. Go back to the opening four lines. In one sentence that uses the word reference, say why reading a[0] showed 99.

Atom 4: == compares the boxes, .equals compares the objects

Now the second half of the idea. When you write p == q, Java compares what is in the boxes. For references that means it asks "the same arrow? the same one object?" It does not ask "do these two objects look alike?" To ask whether two separate objects hold the same contents, you call .equals instead.

String s1 = new String("CSCD");
String s2 = new String("CSCD");
System.out.println(s1 == s2);        // false: two separate objects, two different arrows
System.out.println(s1.equals(s2));   // true: same characters

Recall from Atom 2 that new builds an object in its own place and hands back an arrow to it, so the two new calls here make two separate objects and two different arrows. That is why s1 == s2 is false even though the text matches, while .equals looks at the characters and says true. The rule: use == to ask about identity (same object), use .equals to ask about value (same contents). For text, you almost always want .equals.

(One trap worth naming: Java pools string literals (JLS 3.10.5), so "CSCD" == "CSCD" can be true because both names point at one shared pooled object. That is exactly why == on text is unreliable: its answer depends on how the strings were built, not on what they say. Use .equals and the question goes away.)

Check yourself. For String s1 = new String("CSCD"); String s2 = new String("CSCD");, what does s1 == s2 print, and what does s1.equals(s2) print?

See it in the real Course

Open src/main/java/edu/ewu/cscd211/scheduler/Course.java and find the equals method near the bottom. It is the same idea you just learned, on a real class. The comparison at its heart reads:

return courseCode.equals(other.courseCode)
        && section.equals(other.section)
        && term == other.term;

The two String fields, courseCode and section, are compared with .equals (you want same text, not the same object), but term is compared with ==. That is not a typo. The term field is a Term, which is an enum, and Java creates each enum constant exactly once (JLS 8.9): every mention of Term.SUMMER anywhere in the program is a reference to that one created object. With a single shared object behind every reference, == (same arrow) and .equals (same contents) give the same answer, so == is the idiomatic choice. This is the one case where the new String trap cannot happen, because the language never builds a second copy of an enum constant. You will write this exact equals yourself in Lab 1.

Check yourself. In Course.equals, the String fields are compared with .equals but term is compared with ==. Why is == correct for term when it was wrong for the two Strings?

A failure that makes a rule: an alias is not a copy

The opening bug is the rule. When you write b = a on an object, you did not get a safe private copy; you got a second arrow to the same object, and a change through either name shows through both. So if a method stores an array or a list it was handed, the caller still holds an arrow to that same object and can change the object's insides from outside, behind the object's back. The rule the failure teaches: assignment of a reference shares, it does not copy. When you need an independent copy, you must build one on purpose. Week 2 gives that its name, defensive copy, and shows where leaving it out breaks an object.

Check yourself. A method does this.scores = incoming; where incoming is an int[] the caller passed in. In one sentence, what can the caller still do to this.scores afterward, and why?

A failure that makes a rule: == on text answers the wrong question

Comparing user input or parsed text with == is a bug that sometimes passes by luck. Read this login check:

if (entered == "admin") { ... }   // WRONG: compares arrows, not text

If entered came from new String(...) or from reading a file, its arrow differs from the literal "admin" arrow, so the test is false even when the user typed admin. It might pass during a quick test if both happen to be pooled literals, which is worse, because the bug hides until real input arrives. The rule: compare text with .equals, and reserve == for identity, for null checks, and for enums.

Check yourself. Rewrite if (entered == "admin") so it correctly tests whether the user typed admin, and write the one expression that safely asks whether entered points at no object yet.

Show what you can do

Make this small artifact, then write the reflection. It is neutral on purpose, so the Week 1 lab on Course stays yours to write.

Where this goes next

The diagnostic on June 23 checks this distinction, so a few minutes with the trace above pays off on day one. Week 1 builds directly on it: the rule that compareTo must agree with equals is exactly value-equality (same contents) versus reference-identity (same object), the thing you just learned, applied to Course. Week 2 returns to the alias leak this lesson exposed and gives the fix its name, defensive copy. On the drive in, The Commute Episode 0 sets up why a single shared project, built right from the first object, is how the whole summer holds together.