Objects with attributes you can reach: creating Points and Rectangles, reading their data with dot notation, passing objects to methods and returning new ones, changing an object's attributes, and the aliasing that makes mutable objects genuinely hard to debug. Follows Think Java 2e, Chapter 10 (Mutable Objects), Sections 10.1-10.5, pp. 167-173, cross-referenced against Java SE 21 API — java.awt.Point.
Subject: Java · 65 slides · code lesson
Open the interactive version of this deck
Title
Think Java 2e · Chapter 10 · Mutable Objects
Sections 10.1-10.5 · pp. 167-173
Objectives
This lesson follows Think Java 2e, Chapter 10 (Mutable Objects), Sections 10.1-10.5, pp. 167-173. Everything on these slides can be checked against those pages.
1. Create a Point or Rectangle with new, and access its attributes with dot notation.
2. Explain why there is no conflict between a local variable x and an attribute x.
3. Pass an object to a method, and say why bundling related values is less error-prone.
4. Write a method that returns a newly created object.
5. Modify an object's attributes directly and through a method, and predict the effect on the caller.
6. Draw the memory diagram for two variables referring to one object, and explain what aliasing makes difficult.
Warm-up
Two facts from Chapter 7 are about to become much more consequential.
Discussion prompt
From Lesson 7a: what happens when you assign one array variable to another? And can a method change the contents of an array it was passed?
Hint: One array, two names.
Answer:
The assignment copies the reference, so both variables refer to the same array — they are aliases. And yes, a method can change the contents, because it receives a copy of the reference rather than a copy of the array.
Everything in this chapter is that behaviour, applied to objects rather than arrays. The difference is that Chapter 9's objects — Strings and Integers — were immutable, so aliasing them was harmless. These are not.
Concept
Chapter 9's objects kept their data to themselves: you could call length() on a String but never look inside it. Point and Rectangle are different — their data is public, so you can read it and change it directly.
Figure (svg): A variable named blank with an arrow to a Point object containing two attributes, x holding 3 and y holding 4
Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 10 (Mutable Objects), Sections 10.1-10.5, pp. 167-173 — Chapter 10 opens on printed page 167.
Section
Section 10.1
Concept
The java.awt package provides a class named Point representing a location in a Cartesian plane. You import it, create one with new, and the result of new is a reference to the object.
import java.awt.Point;
Point blank;
blank = new Point(3, 4);
int x = blank.x;
System.out.println(blank.x + ", " + blank.y); // 3, 4
int sum = blank.x * blank.x + blank.y * blank.y; // 25| line | what it does |
|---|---|
| Point blank; | declares that blank has type Point — no object yet |
| blank = new Point(3, 4); | creates the object and makes blank refer to it |
| blank.x | go to the object blank refers to, and get its x |
| blank.x * blank.x + blank.y * blank.y | an ordinary expression using two attributes |
attribute — One of the named data items that make up an object. Also called a field.
dot notation — Use of the dot operator to access an object's attributes or methods.
Variables that belong to an object are called attributes — in some documentation you will see them called fields. The expression blank.x means: go to the object blank refers to, and get the value of the attribute x.
Notation
You have used the dot for methods since Chapter 1. Now it reaches data too, and the reading is the same.
Annotate
blank.x is an attribute and blank.distance(p) is a method — exactly the distinction between a.length and s.length() from Lesson 7a.blank.x on the right of an assignment reads it; on the left it changes the object, which is Section 10.4.int x = blank.x; declares a local x and reads the attribute x on the same line, and there is no conflict — the dot says which one you mean.That last point is worth dwelling on: there is no conflict between the local variable x and the attribute x, because the purpose of dot notation is to identify which variable you are referring to unambiguously.
Worked example
An attribute behaves exactly like any other variable of its type. Anywhere an int can go, blank.x can go.
Point blank = new Point(3, 4);
System.out.println(blank.x + ", " + blank.y);
int sum = blank.x * blank.x + blank.y * blank.y;
System.out.println(sum);| expression | substituting | value |
|---|---|---|
| blank.x | — | 3 |
| blank.y | — | 4 |
| blank.x + ", " + blank.y | 3 + ", " + 4 | the string 3, 4 |
| blank.x * blank.x + blank.y * blank.y | 9 + 16 | 25 |
Read each attribute as you would read a variable.
Why: blank.x is an int with the value 3.
Note the third row.
Why: This is Chapter 2's concatenation rule — the string operand makes the whole expression a string, so the numbers are converted to text.
Note the fourth row.
Why: Pure arithmetic, because no string is involved. 3 squared plus 4 squared is 25.
Note that nothing is modified.
Why: Reading an attribute leaves the object exactly as it was.
Verify: Expect 3, 4 and then 25.
Why: The 25 is worth recognising: it is the square of the distance from the origin to (3, 4), which is 5 — the 3-4-5 triangle from Lesson 4b, appearing again.
Prediction
Reading two attributes.
Point p = new Point(5, 12);
System.out.println(p.x + p.y);| expression | value |
|---|---|
| p.x | 5 |
| p.y | 12 |
| p.x + p.y | 17 — both are ints, so this is addition |
Predict first
What is displayed?
Correct: 17
Why: Both attributes are ints, so + performs addition rather than concatenation — Chapter 2's rule that the operand types decide what + means. To display them separately you would need a string in the expression, as in p.x + ", " + p.y.
Concept
The memory diagram has a specific shape, and every part of it means something you have seen before.
Figure (svg): A variable box labelled blank with an arrow to a Point object whose interior holds two labelled attribute boxes
| part of the diagram | means |
|---|---|
| the name outside the box | the variable's name — as in Chapter 2 |
| the value inside the box | what the variable holds |
| the arrow | the value is a reference |
| the two boxes at the end of it | the object's attributes |
Compare this with a primitive from Lesson 9a: int number = -2; is one box with a value in it. The only difference here is that the value is a reference, so there is something at the other end — and that something has boxes of its own.
Trap
The variable exists; the object does not.
Point blank;
System.out.println(blank.x); // no object to look inside| statement | what exists | result |
|---|---|---|
| Point blank; | a variable that could refer to a Point | no object |
| blank.x | nothing to follow the reference to | compile error: variable blank might not have been initialized |
This is Lesson 7a's uninitialised-array error again. And if the variable had been explicitly set to null, it would compile and throw a NullPointerException instead — Lesson 9a's exception, now reached through an attribute rather than a method.
Declare and create, ideally in one statement.
Point blank = new Point(3, 4);
System.out.println(blank.x); // 3what new does | result |
|---|---|
| allocates memory for the object | the Point exists |
| runs its constructor with (3, 4) | x is 3 and y is 4 — Chapter 11 explains constructors |
| returns a reference | which the assignment stores in blank |
The two-line form is legal and occasionally necessary, exactly as in Lesson 2a — but until new runs, there is no object, and the dot has nothing to look inside.
Definition probe
The parentheses tell you.
Sort into buckets
Sort each expression.
Fill the middle
Import, create, access.
Fill in the blanks
import java.awt.Point;
Point p = new Point(3, 4);
int a = p.x;
Why: new allocates the object and returns a reference to it; without it you would have a variable referring to nothing. The dot then reaches inside that object for the attribute named x — reading as go to the object p refers to, and get its x.
Socratic
The same name in the same statement, meaning two different things.
Discussion prompt
int x = blank.x; declares a local variable called x and reads an attribute called x. Why does the compiler not object, and what would you have to write to get a genuine conflict?
Hint: How many characters distinguish the two names?
Answer:
They are different names: one is x and the other is blank.x. The dot is part of the expression, so there is nothing ambiguous to resolve.
That is exactly what dot notation is for — Think Java says its purpose is to identify which variable you are referring to unambiguously. It is the same reason main's hour and printTime's hour coexisted in Lesson 4a: the compiler always has a way to tell which one you mean.
A genuine conflict would need two things with the same full name in the same scope — two local variables called x, say. That is a compile error, and it is a different situation entirely.
Section
Section 10.2
Concept
You can pass objects as parameters exactly as you pass anything else. The method receives a reference, and can read the object's attributes through it.
public static void printPoint(Point p) {
System.out.println("(" + p.x + ", " + p.y + ")");
}
// printPoint(blank) displays (3, 4)| step | what happens |
|---|---|
| printPoint(blank) | the argument is evaluated — a reference |
| parameter passing | p is assigned a copy of that reference |
| p.x | follows the reference to the same object blank refers to |
| the method returns | p disappears; the Point does not |
This is Lesson 4a's parameter passing, unchanged: the parameter gets a copy of the argument's value, and the value of an object variable is a reference. So p and blank refer to the same Point.
Picture it
While the method runs there are two references to the same Point — one in the caller and one in the method.
Figure (svg): Two variables named blank and p each with an arrow to the same Point object holding x 3 and y 4
Nothing is copied except the arrow. That is why passing a large object to a method is cheap — and, as Section 10.4 will show, why the method can change the object the caller is holding.
Worked example
Lesson 4b's distance took four doubles. Taking two Points instead bundles the related values together.
// before: four separate parameters
public static double distance
(double x1, double y1, double x2, double y2) { ... }
// after: two objects
public static double distance(Point p1, Point p2) {
int dx = p2.x - p1.x;
int dy = p2.y - p1.y;
return Math.sqrt(dx * dx + dy * dy);
}| four doubles | two Points | |
|---|---|---|
| parameters | 4 | 2 |
| can you swap x1 and y1 by mistake? | yes, easily | no — they belong to a Point |
| does the call say what it means? | distance(1, 2, 4, 6) | distance(p1, p2) |
| related values kept together? | no | yes |
Replace the four coordinates with two objects.
Why: Each Point already carries an x and a y that belong together.
Read the attributes inside the method.
Why: p2.x - p1.x — the same arithmetic as before, with the values reached through references.
Note what became impossible.
Why: You can no longer pass the arguments in the wrong order within a point, because a Point's x and y travel as a unit.
Note the call site.
Why: distance(p1, p2) says what it means; distance(1.0, 2.0, 4.0, 6.0) requires the reader to know the convention.
Verify: Call it with (0, 0) and (3, 4) and expect 5.0 — the 3-4-5 triangle again.
Why: Passing objects as parameters makes the source code more readable and less error-prone because related values are bundled together. That sentence is the whole argument for objects as a design idea, stated early.
Prediction
The method modifies an attribute.
public static void bump(Point p) {
p.x = p.x + 10;
}
Point q = new Point(1, 2);
bump(q);
System.out.println(q.x);| step | q.x |
|---|---|
| new Point(1, 2) | 1 |
| bump(q) — p refers to the same object | 1 |
| p.x = p.x + 10 | 11 |
Predict first
What is displayed?
Correct: 11
Why: The parameter receives a copy of the reference, so p and q refer to the same Point — and changing an attribute changes that one object, which the caller can see. Compare this with the int example from Lesson 4a, where bump(x) left the caller's x alone: the difference is entirely that a Point variable holds a reference.
Concept
Both printPoint and distance already exist, which is worth knowing before you write them.
Point p1 = new Point(0, 0);
Point p2 = new Point(3, 4);
double dist = p1.distance(p2); // 5.0
System.out.println(p1);
// java.awt.Point[x=0,y=0]| you might write | already exists as |
|---|---|
| distance(p1, p2) | p1.distance(p2) |
| printPoint(p) | System.out.println(p) |
Point objects provide a method called toString that returns a string representation. When you call println with an object, it automatically calls toString and displays the result — which is why printing a Point gives java.awt.Point[x=3,y=4] rather than the type@address you saw for arrays in Lesson 7a. Chapter 11 shows how to write toString for your own classes.
Trap
The method appears to get its own Point. It does not.
public static void shift(Point p) {
p.x = p.x + 100;
}
Point blank = new Point(3, 4);
shift(blank);
System.out.println(blank.x); // predicted 3| step | blank.x | why |
|---|---|---|
| before the call | 3 | — |
| p receives a copy of the reference | 3 | one object, two references |
| p.x = p.x + 100 | 103 | the object was changed through p |
| after the return | 103 | the change persists |
The output is 103. Lesson 4a said a method cannot change its caller's variables — and that is still true. blank still refers to the same object. What changed is the object itself.
Distinguish changing the variable from changing the object.
public static void reassign(Point p) {
p = new Point(99, 99); // changes only p
}
public static void mutate(Point p) {
p.x = 99; // changes the OBJECT
}| the method does | the caller sees |
|---|---|
| p = new Point(...) | nothing — only the method's own reference moved |
| p.x = 99 | the change, because it is the same object |
This is exactly Lesson 7a's boundary question about arrays: assigning to p moves the arrow; assigning to p.x changes what the arrow points at. The first is local to the method; the second is not.
Definition probe
Assigning to the parameter, or to something the parameter points at?
Sort into buckets
Sort each statement inside a method taking Point p.
Comparison
Fill the blanks from the two versions of distance.
Comparison matrix
| four doubles | two Points | |
|---|---|---|
| the call reads | distance(1.0, 2.0, 4.0, 6.0) | distance(p1, p2) |
| can coordinates be mixed up? | yes — four numbers in a row | no — each x travels with its y |
| what the method receives | four values | two references |
Bundling related values into an object is the first real argument for object-oriented design in this book, and it is a readability argument before it is anything else.
Explain it to yourself
Arrays printed as an address. Points do not.
Discussion prompt
In Lesson 7a, printing an array gave [I@bf3f7e0. Printing a Point gives java.awt.Point[x=3,y=4]. What must be different about the Point class?
Hint: What method does println call on an object?
Answer:
Point provides a toString method that returns a readable string representation, and println automatically calls it. Arrays do not have one, so they fall back on the default — the type and the address.
That is also what Arrays.toString was doing in Lesson 7a: supplying the readable form that arrays lack. And it is why String prints its characters while most objects print an address.
Chapter 11 shows how to write toString for your own classes, which turns out to be one of the most immediately useful things you can add to one.
Section
Section 10.3
Concept
java.awt also provides a class named Rectangle. Rectangles are similar to points but have four attributes: x, y, width and height — and a method can build one object and return another.
import java.awt.Rectangle;
Rectangle box = new Rectangle(0, 0, 100, 200);
System.out.println(box);
// java.awt.Rectangle[x=0,y=0,width=100,height=200]| attribute | value | meaning |
|---|---|---|
| x | 0 | left edge |
| y | 0 | top edge |
| width | 100 | how wide |
| height | 200 | how tall |
Again println uses the toString method provided by Rectangle, which knows how to represent Rectangle objects as strings. Four attributes rather than two, and everything else is the same.
Picture it
The same picture as a Point, with two more boxes inside.
Figure (svg): A variable named box with an arrow to a Rectangle object holding four attributes x, y, width and height
Nothing about the diagram is new. The number of attributes an object has does not change how it is drawn or how it behaves — which is exactly the point of having a consistent model.
Worked example
findCenter takes a Rectangle and returns a Point — a different type from the one it was given.
public static Point findCenter(Rectangle box) {
int x = box.x + box.width / 2;
int y = box.y + box.height / 2;
return new Point(x, y);
}| step | expression | value for (0, 0, 100, 200) |
|---|---|---|
| x | box.x + box.width / 2 | 0 + 50 = 50 |
| y | box.y + box.height / 2 | 0 + 100 = 100 |
| return | new Point(x, y) | a reference to a new Point(50, 100) |
Declare the return type as the type you will return.
Why: public static Point findCenter(...) — Point, not void and not Rectangle.
Read the input object's attributes.
Why: Four reads, combined arithmetically. Note that / 2 is integer division, so odd widths lose half a unit.
Create the new object.
Why: new Point(x, y) allocates it and produces a reference.
Return the reference.
Why: The last line creates a new Point object and returns a reference to it — the object outlives the method, exactly as randomArray's array did in Lesson 7b.
Verify: Call findCenter(new Rectangle(0, 0, 100, 200)) and print the result: java.awt.Point[x=50,y=100].
Why: Then try a rectangle of width 101 and notice the centre comes out at 50 rather than 50.5 — integer division from Lesson 2a, quietly present in a method that looks like geometry.
Prediction
Read the attributes and do the arithmetic.
public static Point findCenter(Rectangle box) {
int x = box.x + box.width / 2;
int y = box.y + box.height / 2;
return new Point(x, y);
}| expression | for (10, 20, 40, 60) | value |
|---|---|---|
| box.x + box.width / 2 | 10 + 20 | 30 |
| box.y + box.height / 2 | 20 + 30 | 50 |
Predict first
For new Rectangle(10, 20, 40, 60), what Point comes back?
Correct: (30, 50)
Why: Half the width is 20 and half the height is 30, added to the corner at (10, 20), giving (30, 50). Note the order of operations from Lesson 2b: division binds tighter than addition, so box.x + box.width / 2 halves the width first rather than adding then halving.
Concept
With objects as both parameters and return values, methods can be composed the way Math methods were in Lesson 4b.
Rectangle box = new Rectangle(0, 0, 100, 200);
Point centre = findCenter(box);
double d = centre.distance(new Point(0, 0));| step | type produced |
|---|---|
| new Rectangle(...) | Rectangle |
| findCenter(box) | Point |
| centre.distance(...) | double |
Each step's output is the next step's input, and the types have to line up — which is exactly the composition rule from Lesson 4b. A method call that returns an object is an expression, so it can go anywhere an object of that type can.
Trap
A worry that turns out not to apply — but it is worth understanding why.
public static Point findCenter(Rectangle box) {
int x = box.x + box.width / 2;
int y = box.y + box.height / 2;
return new Point(x, y);
}
// x and y disappear when the method returns.
// Does the Point disappear too?| when the method returns | what happens |
|---|---|
| the local variables x and y | disappear with the frame |
| the Point object | survives — the caller has a reference to it |
The frame disappears, taking the local variables with it. The object does not, because it was allocated separately and something still refers to it. This is the same situation as randomArray in Lesson 7b.
Objects outlive the method that created them, as long as a reference survives.
Point centre = findCenter(box);
// findCenter's frame is gone.
// centre still refers to the Point it created.| what disappears at return | what survives |
|---|---|
| the method's frame | the object it allocated |
| its parameters and local variables | any object a returned reference points to |
The rule to hold: frames and objects have separate lifetimes. A frame lives from call to return; an object lives as long as something refers to it — which is precisely what Section 10.9's garbage collection is about.
Fill the middle
A method that builds and returns a Point.
Fill in the blanks
public static Point origin() new} Point(0, 0);
}
Why: The return type is the type of object the method promises to produce — Point, exactly as int was for factorial in Lesson 4b. new allocates the object, and what is returned is a reference to it, so the object survives the method's frame disappearing.
Definition probe
Composition only works when the types line up.
Sort into buckets
Sort each expression by its type.
new created an object, or a method returned a reference to one. The value is a reference, and it can be assigned to a variable of that class type.width is an int and distance returns a double.Edge cases
The centre calculation uses integer division.
Discussion prompt
findCenter computes box.x + box.width / 2 with int attributes. What happens for a rectangle of width 101, and is that a bug?
Hint: Lesson 2a.
Answer:
101 / 2 is 50, not 50.5, because both operands are ints and integer division rounds toward zero. So the reported centre is half a unit to the left of the true one.
Whether that is a bug depends on what the method is for. For placing something on a pixel grid it is exactly right — there is no such thing as half a pixel. For a geometric calculation it is a real loss of precision.
The point is that it is a decision, and the code does not say which one was made. Returning a Point forces integer coordinates; if fractional centres mattered, the method would have to return something else. That is a constraint the type imposed, quietly.
Section
Section 10.4
Concept
You can change the contents of an object by making an assignment to one of its attributes. To move a rectangle without changing its size, modify its x and y values.
Rectangle box = new Rectangle(0, 0, 100, 200);
box.x = box.x + 50;
box.y = box.y + 100;
// now at (50, 100, 100, 200)| statement | x | y | width | height |
|---|---|---|---|---|
| new Rectangle(0, 0, 100, 200) | 0 | 0 | 100 | 200 |
| box.x = box.x + 50; | 50 | 0 | 100 | 200 |
| box.y = box.y + 100; | 50 | 100 | 100 | 200 |
mutable — An object that can be modified at any time. Points and rectangles are mutable by design.
An attribute on the left of an assignment writes into the object. This is the first time in the book you have been able to change an object — Strings and Integers in Chapter 9 refused.
Picture it
One object, changed in place. No new Rectangle was created.
Figure (svg): A Rectangle object whose x and y attributes now hold 50 and 100 while width and height are unchanged
Compare this with Lesson 9a's name = name.toUpperCase(), where a second object was created and the variable was pointed at it. Here there is still exactly one object, and it is different from what it was.
Worked example
Lesson 9b's process, applied here: wrap the working lines in a method, then replace the literals with parameters.
public static void moveRect(Rectangle box, int dx, int dy) {
box.x = box.x + dx;
box.y = box.y + dy;
}| call | box before | box after |
|---|---|---|
| moveRect(box, 50, 100) | (0, 0, 100, 200) | (50, 100, 100, 200) |
| moveRect(box, -10, 0) | (50, 100, 100, 200) | (40, 100, 100, 200) |
Wrap the two assignments in a method.
Why: Encapsulation, from Lesson 9b — the lines are unchanged.
Replace the literals with parameters.
Why: dx and dy indicate how far to move the rectangle in each direction.
Note the return type.
Why: void — the method changes the object it was given rather than producing a new one.
Note what the caller sees.
Why: Invoking this method has the effect of modifying the Rectangle that is passed as an argument.
Verify: Call moveRect(box, 50, 100) and print box: java.awt.Rectangle[x=50,y=100,width=100,height=200].
Why: The method returned nothing and yet something changed. That is worth pausing on — modifying objects by passing them as arguments can be useful, but it can also make debugging difficult, because it is not always clear which method invocations modify their arguments.
Prediction
A void method that takes an object.
Rectangle box = new Rectangle(0, 0, 100, 200);
moveRect(box, 50, 100);
System.out.println(box.x);| step | box.x |
|---|---|
| new Rectangle(0, ...) | 0 |
| moveRect modifies the object | 50 |
Predict first
What is displayed?
Correct: 50
Why: The method receives a copy of the reference, so it and the caller refer to the same Rectangle — and assigning to box.x inside the method changes that one object. The method returns nothing and yet the caller's rectangle has moved, which is exactly what makes mutable objects convenient and hard to debug.
Concept
Java provides methods that do this for you — and the way you invoke them is the more idiomatic style.
// passing the object as an argument
moveRect(box, 50, 100);
// invoking a method ON the object
box.translate(50, 100);| moveRect(box, dx, dy) | box.translate(dx, dy) | |
|---|---|---|
| who owns the behaviour | some other class | Rectangle itself |
| the object is | an argument | the thing the method runs on |
| reads as | move this rectangle | rectangle, translate yourself |
| effect | identical | identical |
translate has the same effect as moveRect, but instead of passing the rectangle as an argument you use dot notation. This syntax — using dot notation to invoke a method on an object, rather than passing it as a parameter — is more consistent with the style of object-oriented programming, and Chapter 11 is about writing classes that work this way.
Trap
Two methods, same signature shape, opposite behaviour.
// modifies the argument, returns nothing
public static void moveRect(Rectangle box, int dx, int dy) { ... }
// returns a new object, modifies nothing
public static Point findCenter(Rectangle box) { ... }
// which does this one do?
someMethod(box);| clue | what it suggests |
|---|---|
| returns void | it probably modifies something — otherwise it did nothing |
| returns an object | it probably creates rather than modifies |
| neither is guaranteed | you have to read the method or its documentation |
There is no rule in the language that enforces this. It is not always clear which method invocations modify their arguments, and that is the central difficulty of working with mutable objects.
Use the return type as a signal, and say so in the name.
void moveRect(Rectangle box, int dx, int dy) // modifies
Point findCenter(Rectangle box) // creates
void translate(int dx, int dy) // modifies (on the object)| convention | meaning |
|---|---|
| a void method taking an object | almost certainly modifies it |
| a method returning a new object | almost certainly does not modify its input |
| a verb name like move, set, add | suggests modification |
| a noun-ish name like findCenter, getX | suggests no modification |
These are conventions rather than rules, and they are why naming matters so much for mutable objects. Chapter 9's immutable objects avoided the question entirely — a String method cannot modify anything, so you never have to wonder.
Discrimination
Reading an attribute, or writing one?
Sort into buckets
Sort each statement.
Comparison
Fill the blanks from Chapters 9 and 10.
Comparison matrix
| String (Chapter 9) | Rectangle (Chapter 10) | |
|---|---|---|
| can you assign to its data? | no — no public attributes | yes — box.x = 50 |
| what a method returns | a new object | often void, having modified this one |
| two variables referring to it | completely safe | a hazard — changes are shared |
The bottom row is the subject of the next idea, and it is why Chapter 9 spent a whole chapter on immutability before this chapter introduced its opposite.
Explain it to yourself
A method's return type says something about what it does.
Discussion prompt
A method that takes a Rectangle and returns void has to do something observable, or calling it would be pointless. What are its options, and what does that tell you before you read the body?
Hint: What can a method affect, if it returns nothing?
Answer:
It can print something, or it can modify an object it was given. Those are essentially the only ways a void method can matter — it cannot hand anything back.
So a void method taking an object is a strong hint that it mutates that object. moveRect and translate both fit; so do Arrays.sort and Collections.shuffle, which you will meet in Chapter 13.
The converse is a useful habit too: if you write a void method that neither prints nor modifies anything, it does nothing at all — which is exactly the ignored-return-value bug from Lesson 4b, seen from the other side.
Section
Section 10.5
Concept
When you assign an object to a variable, you are assigning a reference. It is possible to have multiple variables referring to the same object — and now that objects are mutable, that matters.
Rectangle box1 = new Rectangle(0, 0, 100, 200);
Rectangle box2 = box1;
box1.grow(50, 50);
System.out.println(box1);
System.out.println(box2);
// both print x=-50, y=-50, width=200, height=300| statement | objects | box1 | box2 |
|---|---|---|---|
| new Rectangle(0, 0, 100, 200) | 1 | (0,0,100,200) | — |
| Rectangle box2 = box1; | still 1 | (0,0,100,200) | the same object |
| box1.grow(50, 50) | 1 | (-50,-50,200,300) | the same, changed |
grow makes the rectangle bigger by 50 units in all directions: it decreases x and y by 50 and increases width and height by 100. If we print box2 we should not be surprised to see that it has changed too, because it refers to the same object.
Picture it
This is the same picture as Lesson 7a's aliased array. The difference is what happens next.
Figure (svg): Two variables box1 and box2 each with an arrow to the same Rectangle object after grow has changed it
This scenario is called aliasing, because a single object has multiple names that refer to it. As you can tell from this simple example, code that involves aliasing can get confusing fast, and it can be difficult to debug.
Worked example
Two variables referring to one String was completely safe in Chapter 9. Two referring to one Rectangle is not. Compare them directly.
// Chapter 9: immutable
String s1 = "hello";
String s2 = s1;
s1 = s1.toUpperCase(); // s1 points somewhere NEW
System.out.println(s2); // hello - unaffected
// Chapter 10: mutable
Rectangle b1 = new Rectangle(0, 0, 100, 200);
Rectangle b2 = b1;
b1.grow(50, 50); // the OBJECT changes
System.out.println(b2); // changed too| String | Rectangle | |
|---|---|---|
| can the object change? | no | yes |
| what does the method do? | returns a new object | modifies this one |
| what does the second variable see? | the original, unchanged | the change |
| is aliasing a problem? | no | yes |
Notice what changed in each case.
Why: For the String, the variable was reassigned. For the Rectangle, the object was modified.
Notice that both had two references.
Why: Aliasing is present in both examples. Only one of them causes trouble.
Conclude what actually causes the problem.
Why: Not aliasing on its own — aliasing plus mutability. Either one alone is harmless.
Note the design consequence.
Why: Immutable objects can be shared freely; mutable ones need you to know who else holds a reference.
Verify: Run both and confirm s2 is unchanged while b2 is not.
Why: That comparison is the whole answer to why did Chapter 9 spend so long on immutability? — it was building the contrast this section needs.
Prediction
Two variables, one object.
Rectangle box1 = new Rectangle(0, 0, 100, 200);
Rectangle box2 = box1;
box1.translate(10, 20);
System.out.println(box2.x);| step | objects | box2.x |
|---|---|---|
| Rectangle box2 = box1; | 1 | 0 |
| box1.translate(10, 20) | 1 | 10 — the same object |
Predict first
What is displayed?
Correct: 10
Why: The assignment copied the reference, so box1 and box2 are aliases for one Rectangle, and translating it through either name changes the single object both refer to. Counting objects rather than variables is what makes this predictable — there was only ever one.
Concept
The difficulty is not that the behaviour is complicated. It is that the cause and the symptom appear in different places.
| what you see | where the cause is |
|---|---|
| box2 changed and I never touched box2 | a line that mentions only box1 |
| the object changed inside a method | a call that looks like it only reads |
| a 'backup' changed with the original | the assignment that made it, pages earlier |
| two parts of the program disagree | one of them modified shared state |
In each row, searching for the variable that looks wrong finds nothing — the change was made through a different name. **The question to ask is not what changed this variable? but who else has a reference to this object?** That reframing is most of debugging with mutable objects.
Trap
The same mistake as Lesson 7a's array backup, now with an object.
Rectangle original = new Rectangle(0, 0, 100, 200);
Rectangle backup = original; // NOT a copy
original.translate(50, 50);
System.out.println(backup);
// x=50, y=50 - the "backup" moved too| step | objects in existence | backup shows |
|---|---|---|
| Rectangle backup = original; | 1 | (0, 0, ...) |
| original.translate(50, 50) | 1 | (50, 50, ...) — changed |
There was never a backup. The assignment copied the reference, so both names point at the one Rectangle — and the original values are gone.
Create a second object.
Rectangle original = new Rectangle(0, 0, 100, 200);
Rectangle backup =
new Rectangle(original.x, original.y,
original.width, original.height);
original.translate(50, 50);
System.out.println(backup); // x=0, y=0 - unaffected| approach | objects | independent? |
|---|---|---|
| backup = original | 1 | no — aliases |
| new Rectangle(original.x, ...) | 2 | yes |
The test that distinguishes them is the same one from Lesson 7a: change one and look at the other. And the same question applies — count objects, not variables.
Definition probe
It depends entirely on whether the object can change.
Sort into buckets
Sort each situation.
Invariant
Step through the aliasing example, counting objects at each frame.
Step through it
At which frame does a second object come into existence?
None of them. There is exactly one Rectangle throughout, and that is the whole explanation — every surprise in this example comes from expecting there to be two.
Counterexample
Chapter 9 argued hard for immutability. Argue the other way.
Discussion prompt
If Rectangle were immutable, translate would have to return a new Rectangle and aliasing would be harmless. Why do you think Java's designers made it mutable instead?
Hint: Think about a program moving a hundred rectangles sixty times a second.
Answer:
Efficiency and naturalness. Sometimes it is more efficient to modify an existing object than to create a new one, and some computations are expressed more naturally using mutation — move this rectangle really is an action performed on a thing.
A graphics program moving many shapes many times a second would allocate an enormous number of short-lived objects under an immutable design, all of which would then have to be collected.
Think Java's own conclusion is the right one: neither design is always better, which is why you will see both. What matters is knowing which kind of object you are holding — and Section 10.10 makes that explicit.
Comparison
Four operations on an object parameter. Fill the blanks.
Comparison matrix
| inside a method taking Rectangle box | changes the object? | the caller sees it? |
|---|---|---|
| int w = box.width; | no | there is nothing to see |
| box.width = 50; | yes | yes |
| box.translate(5, 5); | yes | yes |
| box = new Rectangle(...); | no — a different object | no — only the method's own reference moved |
The last row is the one people get wrong in both directions. Assigning to the parameter moves the arrow; assigning to an attribute changes what the arrow points at.
Pattern
Every question about objects in this chapter is answered by drawing the diagram and counting the objects.
Rectangle a = new Rectangle(0, 0, 100, 200); // 1 object
Rectangle b = a; // still 1
Rectangle c = new Rectangle(a.x, a.y,
a.width, a.height); // now 2
a.translate(10, 10);
// b moved. c did not.| question | how to answer it |
|---|---|
| will this change be visible elsewhere? | count the references to the object |
| is this a copy or an alias? | count the objects, not the variables |
| does this method modify its argument? | look for an attribute on the left of an assignment |
| why did a variable I never touched change? | ask who else has a reference |
Check
Work it out before you click.
Point p = new Point(6, 8);
System.out.println(p.x * p.y);| expression | value |
|---|---|
| p.x | 6 |
| p.y | 8 |
| p.x * p.y | 48 |
Check your understanding
What is displayed?
Answer: A
Why: Both attributes are public ints, so p.x * p.y is ordinary multiplication of 6 and 8. Point's attributes are deliberately public, which is what lets you reach them with dot notation — Chapter 11 introduces private attributes and the reasons for preferring them.
Check
Work it out before you click.
public static void widen(Rectangle r) {
r.width = r.width + 10;
}
Rectangle box = new Rectangle(0, 0, 100, 200);
widen(box);
System.out.println(box.width);| step | box.width |
|---|---|
| new Rectangle(...) | 100 |
| r refers to the same object | 100 |
| r.width = r.width + 10 | 110 |
Check your understanding
What is displayed?
Answer: A
Why: The parameter receives a copy of the reference, so r and box refer to the same Rectangle, and assigning to r.width changes that one object. The method returns void and still has a visible effect, which is what makes mutable objects both convenient and hard to track.
Check
Work it out before you click.
Rectangle a = new Rectangle(0, 0, 10, 10);
Rectangle b = a;
Rectangle c = new Rectangle(0, 0, 10, 10);
a.translate(5, 5);| variable | refers to | x after |
|---|---|---|
| a | the first object | 5 |
| b | the same first object | 5 |
| c | a second object | 0 |
Check your understanding
What are b.x and c.x?
Answer: A
Why: b = a copies the reference, so b is an alias and sees the translation. c was created with its own new, so it is a separate object with the same initial values and is unaffected. Two objects exist, and only one of them moved.
new created a second, independent object.Real world
Aliasing bugs share a signature: a value changes and the line that changed it never mentions the variable you are watching.
Discussion prompt
You are debugging a program where a Rectangle's width changes unexpectedly between two of your own lines. Searching for that variable's name finds nothing that assigns to it. What is your next move?
Hint: The variable did not change. Something else did.
Answer:
Find every other reference to that object. Where was it created, what was it assigned to, and what methods was it passed to? Any of those could have modified it through a different name.
That is why the memory diagram is the debugging tool here rather than the source code. The source shows names; the diagram shows objects, and the object is what changed.
The practical defence is to limit sharing. Prefer immutable objects where you can; where you cannot, copy an object before handing it to something you do not control, and be deliberate about which methods modify their arguments. Chapter 11 gives you the tool that makes this enforceable — private attributes.
Commit first
Commit to an answer and to your confidence.
Predict first
A method takes a Rectangle and executes box = new Rectangle(0, 0, 1, 1);. What does the caller see?
Correct: Nothing — only the method's own parameter variable was reassigned
Why: Parameter passing copies the reference, so the method has its own variable pointing at the caller's object. Assigning a new object to that variable moves the method's arrow and leaves the caller's variable pointing where it always did. Contrast this with box.x = 0;, which follows the arrow and changes the shared object — and the caller sees that immediately. This is exactly the boundary question from Lesson 7a: assigning to the parameter moves the arrow; assigning to an attribute changes what it points at.
Explain it
Two minutes, drawing as you go.
Discussion prompt
A classmate wrote Rectangle backup = original; and cannot understand why moving the original also moved the backup. Draw the diagram and explain. Then explain why the same mistake with a String would have been harmless.
Hint: Count objects in both cases.
Answer:
Draw two variable boxes and one Rectangle. The assignment copied the arrow, not the object, so both names lead to the same thing — and translating it through either name changes the one object.
With a String the same assignment also gives two names for one object. But a String cannot be changed, so there is no way for one name to affect what the other sees. The only thing you can do is point one variable at a different string, which leaves the other alone.
The sentence worth landing on: aliasing is only a problem when the object is mutable. That is why Chapter 9 came first.
Exit ticket
One question before you close the deck.
Predict first
Why is aliasing a hazard for Rectangles but not for Strings?
Correct: Because Rectangles are mutable, so a change through one reference is visible through every other
Why: Both Strings and Rectangles are objects, and both can have several variables referring to one of them — aliasing happens equally in each case. The difference is that a Rectangle's attributes can be modified, so a change made through one name appears through all of them, while a String cannot be changed at all. The hazard is aliasing plus mutability; either one on its own is harmless, which is exactly why immutable objects can be shared freely.
Connect it up
One page, from memory.
Draw it
Draw three diagrams. First: Rectangle a = new Rectangle(0, 0, 100, 200); — one variable, one object, four attributes labelled. Second: after Rectangle b = a; — mark clearly how many objects exist. Third: after a.translate(50, 50); — show what changed and note what b.x now reports. Underneath, write the two lines box = new Rectangle(...) and box.x = 0 as they would appear inside a method, and say for each whether the caller sees anything.
Recap
Five sections that introduce objects whose data you can reach and change — and the debugging difficulty that comes with it.
| if you remember one thing | it is this |
|---|---|
| about attributes | the dot reaches data as well as methods |
| about parameters | the reference is copied, not the object |
| about debugging | ask who else has a reference |
blank.x, box.width.box.translate(dx, dy) is the object-oriented style; passing the object as an argument is not.Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.