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.
- [ ] I can declare a variable and assign it a value.
- [ ] I can create an object with
newand store it in a variable. - [ ] I can tell a primitive type (
int,double,boolean,char) from a class or array type (String,int[], a class I wrote).
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.
- Artifact. In a tiny
main, do two traces. First: makeint[] original = {10, 20};, alias it withint[] copy = original;, runcopy[0] = 99;, and printoriginal[0]. Predict the output on paper first, then run it and confirm. Second: buildString a = new String("hi");andString b = new String("hi");, and print botha == banda.equals(b). Predict both, then confirm. - Reflection ("I can ..."). Write one line for each, in your own words: I can say what a primitive box
holds versus what an object box holds; I can explain why
b = amakes an alias and not a copy; I can say when to use==and when to use.equals, and name the two places where==is the right tool. Then name one CSCD 210 bug you remember that you now understand was an aliasing or an==bug.
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.