Class Diagrams, Scope, Garbage Collection, and StringBuilder

Reading the Java library's own source, summarising a class with a UML diagram, the three kinds of variable a method can see, what happens to an object nobody refers to any more, and the mutable class that exists because building a long string out of immutable ones is wasteful. Follows Think Java 2e, Chapter 10 (Mutable Objects), Sections 10.6-10.11, pp. 173-179, cross-referenced against Java SE 21 API — java.lang.StringBuilder.

Subject: Java · 65 slides · code lesson

Open the interactive version of this deck

What this lesson covers

The lesson, slide by slide

1. Class Diagrams, Scope, Garbage Collection, and StringBuilder

Title

Think Java 2e · Chapter 10 · Mutable Objects

Sections 10.6-10.11 · pp. 173-179

2. What you will be able to do

Objectives

This lesson follows Think Java 2e, Chapter 10 (Mutable Objects), Sections 10.6-10.11, pp. 173-179. Everything on these slides can be checked against those pages.

1. Locate and skim the Java library source, and say what most of Point.java consists of.

2. Read and draw a UML class diagram, including the public and private markers.

3. Distinguish parameters, local variables and attributes by when they are created and where they can be used.

4. Explain the order in which the compiler resolves a variable name.

5. Say when an object becomes eligible for garbage collection.

6. Choose between String and StringBuilder, and explain the cost of concatenating in a loop.

3. Retrieve before you read

Warm-up

One idea from Chapter 4 and one from Chapter 9, both about to be extended.

Discussion prompt

From Lesson 4a: what is the scope of a local variable, and when does it disappear? And from Lesson 9a: what does it mean that Strings are immutable?

Hint: One is about frames; one is about objects that never change.

Answer:

A local variable can be used only inside its own method, and it disappears when the method returns — with its frame. Immutable means no method can change the object; every String method returns a new one.

This lesson adds a third kind of variable that outlives any method, and shows what immutability costs when you build a long string one piece at a time.

4. Looking at classes from the outside and the inside

Concept

The previous lesson used Point and Rectangle. This one looks at how they are described — as a diagram, as source code, and in terms of what lives how long.

Figure (svg): UML class diagrams for Point and Rectangle showing their attributes and the methods introduced in this chapter

Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 10 (Mutable Objects), Sections 10.6-10.11, pp. 173-179 — Sections 10.6-10.11, printed pages 173-179.

5. The Java library source

Section

Section 10.6

6. The library is written in Java, and you can read it

Concept

So far you have used several classes from the Java library — System, String, Scanner, Math and Random. These classes are written in Java, so you can read the source code to see how they work.

// the library source ships as a ZIP archive named src.zip
// Linux:    /usr/lib/jvm/.../lib
// macOS:    /Library/Java/JavaVirtualMachines/.../Contents/Home/lib
// Windows:  C:\Program Files\Java\...\lib
openand you find
src.zipfolders corresponding to Java packages
the java folder, then awtPoint.java, Rectangle.java and the rest of java.awt
Point.javathe actual source of the class you used yesterday

The library contains thousands of files, many of them thousands of lines long — that is more than one person could read and understand fully, but do not be intimidated. You are skimming, not auditing.

7. What Point.java actually contains

Notation

Open it and skim. It uses language features you have not met, and the proportions are the interesting part.

Annotate

  • Notice how much of the file is documentation. Each method includes comments and tags like @param and @return — often more lines of comment than of code.
  • Javadoc reads those comments and generates HTML. You can see the same documentation online by searching for Java Point — the web page and the source comments are the same text.
  • You will not understand every line, and that is expected. You can still get a sense of what professional Java source looks like by browsing.
  • Look at grow and translate in Rectangle. Think Java's own comment: there is more to them than you may have expected — real library methods handle overflow and edge cases your version would not.

Appendix B covers Javadoc properly. The point here is simply that the library is not a black box: it is Java, written by people, and you can look.

8. Reading a method you already use

Worked example

Compare your own moveRect from Lesson 10a with the library's translate. Both move a rectangle, and one of them is much longer.

// yours, from Lesson 10a
public static void moveRect(Rectangle box, int dx, int dy) {
    box.x = box.x + dx;
    box.y = box.y + dy;
}

// the library's, in outline
public void translate(int dx, int dy) {
    int oldv = this.x;
    int newv = oldv + dx;
    if (dx < 0) {
        ...           // overflow handling
    }
    ...
}
moveRecttranslate
lines2dozens
handles ordinary movesyesyes
handles integer overflownoyes
documented with Javadocnoextensively

Notice both do the same thing for ordinary inputs.

Why: Adding dx to x is the whole idea, and yours implements it correctly.

Notice what the library version adds.

Why: What happens if x + dx exceeds Integer.MAX_VALUE? Lesson 9a's constants exist because that limit is real.

Notice this is not a criticism of your version.

Why: For a program that moves rectangles by small amounts, moveRect is correct and clearer.

Draw the general lesson.

Why: Library code handles cases your version does not, which is a reason to prefer it — and a reason library code is longer than you expect.

Verify: Find Rectangle.java in src.zip and read translate. Count the lines and count the comments.

Why: This is worth doing once. It permanently changes how you read the phrase just use the library method, and it makes the advice from Lesson 4b — look for it before writing your own — concrete rather than abstract.

9. What is most of Point.java?

Prediction

Open it and look at the proportions.

Predict first

Skimming the file, what makes up the largest part of it?

  • Documentation comments with tags like @param and @return
  • The x and y attribute declarations
  • Import statements
  • The main method

Correct: Documentation comments with tags like @param and @return

Why: Think Java points this out explicitly: notice how much of Point.java is documentation. Each method carries comments and Javadoc tags, and the tool reads those to generate the HTML documentation you find online — so the source and the docs are literally the same text. Point.java has no main method at all, because it is a class to be used rather than a program to be run.

10. Documentation as part of the source

Concept

The comments in Point.java are not ordinary // comments. They start with /** and carry structured tags, and a tool turns them into the documentation you have been reading online.

comment styleread byused for
// line commentpeople reading the sourceexplaining a line
/* block comment */people reading the sourceexplaining a section
/** doc comment */people AND the Javadoc toolgenerating the API documentation
@param, @returnthe Javadoc tooldescribing parameters and return values

You saw a doc comment in Lesson 3b's Convert program without a name for it. Appendix B is entirely about this, and the practical consequence is that the documentation you look up and the comments in the source are the same words — which is why library documentation is usually accurate.

11. Assuming a library method is simple because its job is

Trap

The trap

Writing your own because it looks like two lines.

// "I'll just write my own instead of importing Rectangle"
public static void grow(Rectangle r, int h, int v) {
    r.x = r.x - h;
    r.y = r.y - v;
    r.width = r.width + 2 * h;
    r.height = r.height + 2 * v;
}
caseyour versionthe library's
ordinary valuescorrectcorrect
width near Integer.MAX_VALUEsilently overflows to a negativehandled
negative growth larger than the rectangleproduces a negative widthhandled

The first row is the one you will test. The other two are why Rectangle.grow is longer than four lines — and why Think Java tells you to go and look at it.

The fix

Use the library, and read it when you are curious.

box.grow(50, 50);       // tested, documented, handles the edges
reason to use the library version
it handles cases you have not thought ofoverflow, negatives, extremes
it is documentedwith Javadoc you can read online
it says what it meansgrow, rather than four assignments
it is already testedby rather more people than you

This is Lesson 4b's advice with evidence attached. Writing it yourself is how you understand it; using the library version is how you write correct code — and now you can go and read the difference.

12. Where would you find each class?

Definition probe

The folders inside src.zip correspond to packages.

Sort into buckets

Sort each class by its package.

java.awt
Point; Rectangle
java.util
Scanner
java.lang
String
java.math
BigInteger
awt
The Abstract Window Toolkit — graphics and geometry classes, which is why they must be imported.
util
Utility classes, including Scanner, Random and Arrays.
lang
Classes fundamental to the language, imported automatically — which is why String needs no import.
math
Arbitrary-precision arithmetic, including BigInteger and BigDecimal.

13. Why is the library open to read?

Real world

It could have shipped as compiled byte code only.

Discussion prompt

The Java library ships its source alongside the compiled classes. What does that make possible, and what would be harder if only the .class files were available?

Hint: Think about the last time a library method surprised you.

Answer:

It lets you answer questions the documentation does not. When a method behaves unexpectedly at an edge case, the source settles it — and it settles it faster than guessing or experimenting.

It is also how you learn what good code looks like. Most programmers read far more code than they write, and library source is professional code you can read at any time.

Without it you would be limited to the documentation plus experiment. That works, and it is slower — and it makes some questions, like why is grow so long?, effectively unanswerable.

14. What does 'more than you may have expected' mean?

Explain it to yourself

Think Java's comment about grow and translate.

Discussion prompt

The book says of Rectangle's grow and translate that there is more to them than you may have expected. What kinds of thing do you now think a library method has to handle that a two-line version would not?

Hint: What are the limits of an int?

Answer:

Overflow, mainly. x + dx can exceed Integer.MAX_VALUE and wrap around to a negative number — Lesson 9a's constants exist because that boundary is real, and a library used by millions of programs cannot ignore it.

Also degenerate cases: shrinking a rectangle by more than its own size, or growing one so large its width overflows. Your version produces nonsense; the library's makes a defined choice.

The general shape: library code is longer than your version because it handles inputs you have not thought of yet. That is what tested means, and it is the strongest argument for preferring it.

15. Class diagrams

Section

Section 10.7

16. A box with attributes and methods

Concept

Point and Rectangle objects have attributes and methods — attributes are an object's data, methods are its code. An object's class definition specifies which it has, and UML defines a graphical way to summarise that.

Figure (svg): A UML class diagram for Point showing two int attributes and one method, with plus signs marking them public

UML — Unified Modeling Language, a standard way to draw diagrams for software engineering.

class diagram — An illustration of the attributes and methods for a class.

Each class is a box with the name of the class, a list of attributes, and a list of methods. Both Point and Rectangle have additional methods; the diagram shows only the ones introduced in this chapter.

17. Reading the notation

Notation

Three conventions, and the first one catches Java programmers out because it is deliberately not Java.

Annotate

  • UML uses a language-independent syntax, so it writes x: int rather than Java's int x. That is deliberate — the same diagram can describe a class in any language.
  • The plus sign identifies public attributes and methods. Everything on Point and Rectangle in this chapter is public, which is exactly why you could write blank.x.
  • A minus sign identifies private attributes and methods, which Chapter 11 introduces. Private means other classes cannot reach them — the opposite of what made Lesson 10a possible.
  • A class diagram visualises the source code at compile time, in contrast to memory diagrams, which visualise objects and variables at run time.

That last distinction is the important one. A memory diagram shows these particular objects, right now; a class diagram shows what any object of this class has.

18. Drawing Rectangle

Worked example

Four attributes and two methods, using the conventions above.

// From the chapter:
//   attributes: x, y, width, height   - all int, all public
//   methods:    translate(int, int)
//               grow(int, int)
what you knowhow it appears in UML
the class is called Rectanglethe name, at the top of the box
it has four int attributes+ x: int, and three more
they are publicthe plus sign
translate takes two ints and returns nothing+ translate(int, int)

Write the class name in the top compartment.

Why: Just the name — no keyword, no package.

List the attributes in the middle compartment.

Why: + x: int — visibility, name, colon, type.

List the methods in the bottom compartment.

Why: + translate(int, int) — parameter types only, since the names are not part of the interface.

Omit what is not relevant.

Why: Rectangle has many more methods; showing only the ones under discussion is normal and expected.

Verify: Compare your diagram with the one on the big-idea slide of this lesson.

Why: Then notice what the diagram does not tell you: it says a Rectangle has a width, not what any particular rectangle's width is. That is the compile-time against run-time distinction, made concrete.

19. What does the plus sign mean?

Prediction

UML visibility markers.

Point
+ x: int
+ y: int
markermeaning
+public
-private

Predict first

What does + x: int tell you?

  • x is a public attribute of type int
  • x is a private attribute of type int
  • x must be added to something
  • x is a method returning an int

Correct: x is a public attribute of type int

Why: The plus sign marks public visibility, the name comes before the colon and the type after it — UML's language-independent syntax. That x is public is exactly why Lesson 10a could write blank.x; Chapter 11's private attributes, marked with a minus, could not be reached that way.

20. Class diagrams and memory diagrams

Concept

The two kinds of diagram in this book answer different questions, and confusing them is a common early mistake.

class diagrammemory diagram
showsthe source code's structureobjects and variables
whencompile timerun time
how many boxes for Rectangleone, everone per object that exists
containsattribute names and typesattribute values
answerswhat does a Rectangle have?what is in THIS rectangle now?

The third row is the clearest test. A program with fifty Rectangles has fifty boxes in its memory diagram and still one in its class diagram — because there is only one Rectangle class.

21. Drawing values in a class diagram

Trap

The trap

Mixing the two kinds of diagram. Values belong to objects, not to classes.

// wrong - this is a memory diagram wearing a class diagram's clothes
Rectangle
------------------
+ x = 0
+ y = 0
+ width = 100
+ height = 200
what is wrongwhy
values instead of typesthe class has no values — objects do
it describes one rectanglea class describes all of them
no type informationwhich is what the diagram is for

This diagram cannot be right for two rectangles at once, which is the giveaway. A class diagram has to be true of every object of that class.

The fix

Types in the class diagram; values in the memory diagram.

// class diagram: what every Rectangle has
Rectangle
+ x: int
+ width: int

// memory diagram: what this one holds
box ---> Rectangle [ x = 0, width = 100 ]
questionwhich diagram
does Rectangle have a height?class diagram
what is box's height right now?memory diagram
how many objects exist?memory diagram
is width public?class diagram

A quick test when drawing: could this box be true of two different objects at the same time? If not, you have drawn a memory diagram.

22. Class diagram or memory diagram?

Discrimination

Which question is being asked?

Sort into buckets

Sort each question by the diagram that answers it.

class diagram
does a Rectangle have a width?; is translate public?
memory diagram
what is box1's width right now?; how many Rectangle objects exist?; do box1 and box2 refer to the same object?
cls
It is a question about the source code — what the class declares — and the answer is the same however many objects exist.
mem
It is a question about particular objects at a particular moment, which only a run-time picture can answer.

23. Write a UML attribute line

Fill the middle

A public int attribute called height.

Fill in the blanks

+ height: int

Why: The plus sign marks it public, and UML puts the name before the type separated by a colon — the reverse of Java's int height. The notation is deliberately language-independent so the same diagram can describe a class written in any language.

24. What can a class diagram not tell you?

Counterexample

It summarises a class completely, in one sense.

Discussion prompt

A UML class diagram lists every attribute and method of a class. What important things about a program does it still not show — and when would that matter?

Hint: Think about what you have to draw to debug an aliasing bug.

Answer:

It shows no values, no objects, and no relationships between particular objects — so it cannot show aliasing, or how many rectangles exist, or which variables point where.

It also shows no behaviour: translate(int, int) appears, but not what it does. For that you need the documentation or the source.

So the two diagram types are complementary rather than competing. A class diagram is a map of the code; a memory diagram is a photograph of the run. Lesson 10a's aliasing bug needed the second, and no amount of the first would have helped.

25. Scope revisited

Section

Section 10.8

26. Three kinds of variable

Concept

Section 4.5 introduced the idea that variables have scope — the part of a program where a variable can be used. The library's Rectangle.translate uses all three kinds at once.

public void translate(int dx, int dy) {
    int oldv = this.x;
    int newv = oldv + dx;
    if (dx < 0) {
        ...
kindin this examplecreated whendisappears when
parameterdx, dythe method is invokedthe method returns
local variableoldv, newvthe method is invokedthe method returns
attributethis.xthe object is createdthe object is destroyed

Parameters and local variables are created when a method is invoked and disappear when it returns. They can be used anywhere inside the method, but not in other methods and not in other classes.

27. Two lifetimes, side by side

Picture it

A frame lives from call to return. An object lives as long as something refers to it. Those are different clocks.

Figure (svg): Two panels comparing the lifetime of parameters and locals against the lifetime of attributes

Attributes are created when an object is created and disappear when the object is destroyed. They can be used in any of the object's methods using the keyword this — and if they are public, in other classes via a reference, as box1.x.

28. How the compiler resolves a name

Worked example

When the Java compiler encounters a variable name, it searches backward for its declaration — and the order it searches in is fixed.

public void translate(int dx, int dy) {
    int oldv = this.x;
    int newv = oldv + dx;
    ...
}
namefound asat which step
oldva local variable1 — locals first
dxa parameter2 — then parameters
this.xan attribute, named explicitlythe dot removes the search
a name matching all threethe local variable wins1

The compiler looks for local variables first.

Why: Declared inside the method body.

Then parameters.

Why: Declared in the method's header.

Then attributes.

Why: Declared in the class, outside any method.

Note why this.x is written explicitly.

Why: Writing this.x says the attribute, regardless of what local names exist — the same disambiguation dot notation provided in Lesson 10a.

Verify: Check the order by asking: if a method had a local variable named x, what would a bare x refer to?

Why: The local one, because locals are searched first. That is exactly why the library writes this.x rather than x — it removes any doubt, and it keeps working if someone later adds a local called x.

29. Which kind of variable?

Definition probe

Where it is declared decides what it is.

Sort into buckets

Sort each variable from Rectangle.translate.

a parameter
dx, in the method header; dy, in the method header
a local variable
oldv, declared in the body; newv, declared in the body
an attribute
this.x
par
Declared in the method's header, and given its value by the caller at the moment of the call.
loc
Declared inside the method body. Created on invocation and discarded on return, like a parameter.
att
Belongs to the object rather than to any method, so it exists as long as the object does and is visible to all its methods.

30. What this means

Concept

this is a reference to the object the method was invoked on. It is the missing piece that connects Lesson 10a's box.translate(50, 100) to the method's body.

box.translate(50, 100);

// inside translate:
//   this   refers to the object box refers to
//   this.x is that object's x attribute
//   dx     is 50
at the call siteinside the method
boxthis
box.xthis.x
the first argument, 50the parameter dx

This is why invoking a method on an object works at all: the object comes in as this, without appearing in the parameter list. Chapter 11 makes this central when you write your own classes; for now it is enough to read it as the object this method was called on.

31. Expecting a local variable to persist

Trap

The trap

A local variable does not survive the method.

public void translate(int dx, int dy) {
    int oldv = this.x;      // local
    this.x = oldv + dx;
}
// oldv is gone the moment translate returns.
// Nothing outside can see it, and the next call gets a fresh one.
variableafter the method returns
oldvgone
dxgone
this.xstill there — it belongs to the object

This is Lesson 4a's rule, unchanged. What is new is the third row: an attribute outlives the method that changed it, which is exactly what makes an object able to remember anything.

The fix

Use an attribute for anything the object must remember.

// a value needed only during this call  -> local variable
int oldv = this.x;

// a value the object must keep              -> attribute
this.x = oldv + dx;
the value must lastdeclare it as
for one statementa local variable
for one method calla local variable
as long as the objectan attribute

That table is the whole decision, and it is the same one as Lesson 7a's the accumulator goes outside the loop — one level up. Scope should match how long the value needs to live.

32. Order the compiler's search

Ranking

When it meets a bare variable name.

Put in order

  1. local variables
  2. parameters
  3. attributes

Why: The compiler first looks for local variables, then parameters, then attributes — innermost outward. That is why a local variable named x would hide an attribute named x, and why library code writes this.x explicitly rather than relying on the search finding the right one.

33. Which x does this refer to?

Prediction

A local variable shares a name with an attribute.

public void shift(int dx) {
    int x = 100;          // a local variable
    this.x = x + dx;      // which x is which?
}
writtenresolves to
xthe local variable, 100
this.xthe attribute, explicitly

Predict first

If the object's x was 5 and dx is 3, what does the attribute become?

  • 103
  • 8
  • 105
  • a compile error

Correct: 103

Why: The bare x resolves to the local variable, because the compiler searches locals first — so the expression is 100 + 3. this.x names the attribute explicitly and is what receives the result. The object's original x of 5 is never read, which is exactly the confusion this exists to prevent.

34. Why does the library write this.x?

Explain it to yourself

Inside a method, a bare x would usually find the attribute anyway.

Discussion prompt

In Rectangle.translate, the author wrote this.x rather than x. Since there is no local variable called x, both would work. Why write the longer form?

Hint: What happens if someone edits the method later?

Answer:

It says what it means, and it keeps working. A bare x relies on no local or parameter having that name — and someone adding one later would silently change the meaning of every bare x in the method.

It also makes the code readable without holding the whole method in your head: this.x is unmistakably the object's data, while x requires you to check.

This is the same argument as always writing braces in Lesson 5a: prefer the form that cannot be silently broken by a later edit. It costs five characters.

35. Garbage collection

Section

Section 10.9

36. When does an object cease to exist?

Concept

Attributes exist as long as the object exists. But when does an object stop existing? The answer is: when nothing refers to it any more.

Point blank = new Point(3, 4);
blank = null;
statementreferences to the Pointcan you reach it?
new Point(3, 4)1 — blankyes
blank = null;0no

garbage collection — The process of finding objects that have no references and reclaiming their storage space.

The first line creates a Point and makes blank refer to it. The second changes blank so that instead of referring to the object, it refers to nothing. After the second assignment there are no references to the Point object.

37. An object nobody can reach

Picture it

The object is still in memory, taking up space. From the program's point of view it has ceased to exist.

Figure (svg): A variable holding null with no arrow, and a Point object with nothing pointing at it

If there are no references to an object, there is no way to access its attributes or invoke a method on it. As the program runs, the system automatically looks for such stranded objects and deletes them, so the space can be reused.

38. Three ways an object becomes garbage

Worked example

Setting a variable to null is the clearest case and the rarest. Objects usually become unreachable for one of two other reasons.

// 1. the reference is overwritten
Point p = new Point(3, 4);
p = new Point(5, 6);          // the first Point is now garbage

// 2. the reference goes out of scope
public static void f() {
    Point q = new Point(1, 2);
}                             // q disappears; the Point is garbage

// 3. explicitly, with null
Point r = new Point(7, 8);
r = null;
causewhat happened to the referencecommon?
overwrittenthe variable now points elsewherevery
went out of scopethe frame was discardedvery
set to nullexplicitly clearedrare

Count the references, not the variables.

Why: An object is garbage when the count reaches zero — however that happened.

Notice case 2 is the usual one.

Why: Every method that creates an object and does not return it leaves garbage behind when it returns.

Notice that you never delete anything.

Why: You do not have to do anything to make garbage collection happen, and in general you do not have to be aware of it.

Notice what you might notice.

Why: In high-performance applications you may see a slight delay now and then while Java reclaims space.

Verify: Trace Lesson 10a's findCenter: it creates a Point and returns it, so that Point is not garbage — the caller holds a reference.

Why: Then trace a version that creates a Point and does not return it. That object becomes unreachable the instant the method returns, and you never had to think about it. That is the whole feature.

39. Is the first Point garbage?

Prediction

The reference is overwritten.

Point p = new Point(3, 4);
p = new Point(5, 6);
objectreferences after the second line
Point(3, 4)0
Point(5, 6)1 — p

Predict first

What happens to the Point holding (3, 4)?

  • Nothing refers to it, so it becomes eligible for garbage collection
  • It is deleted immediately by the second line
  • It stays reachable through p
  • A compile error — p already refers to a Point

Correct: Nothing refers to it, so it becomes eligible for garbage collection

Why: Reassigning p moves its arrow to the new object, leaving the first with no references at all. It is not deleted at that instant — the system looks for stranded objects as the program runs and reclaims them when it does. Being eligible and being collected are different moments.

40. Why this is worth having

Concept

In languages without garbage collection, the programmer frees memory explicitly — and the two ways of getting that wrong are both serious.

mistakewhat happensin Java
forget to freea memory leak — the program grows until it failscannot happen this way
free too earlya reference to freed memory; unpredictable behaviourcannot happen
free twicecorruptioncannot happen

Java removes an entire category of bug by taking the decision away from you. The cost is the occasional pause while collection runs, and less control over exactly when memory is reclaimed — which is why some systems still use languages that make you do it by hand.

41. Thinking null deletes the object

Trap

The trap

Setting a variable to null does not destroy anything.

Point a = new Point(3, 4);
Point b = a;
a = null;

System.out.println(b.x);      // still 3
stepreferences to the Point
Point b = a;2 — a and b
a = null;1 — b still refers to it
is it garbage?no

Assigning null changes one variable. The object is collectable only when nothing at all refers to it — which is a fact about the whole program, not about any single line.

The fix

An object is garbage when its last reference goes.

Point a = new Point(3, 4);
Point b = a;
a = null;
b = null;                     // now it is unreachable
afterreferencescollectable?
a = null;1no
b = null;0yes

This is the aliasing lesson again, from a different angle: count the references to the object, not the assignments to variables. It is the same question that decided whether b2 saw box1.grow.

42. Reachable or garbage?

Definition probe

Count the references to the object.

Sort into buckets

Sort each situation after the code shown.

eligible for collection
Point p = new Point(1,2); p = null;; a method creates a Point and returns void
still reachable
Point p = new Point(1,2); Point q = p; p = null;; a method creates a Point and returns it
gc
No reference to the object survives, so nothing can reach it and its space can be reclaimed.
live
At least one reference remains — another variable, or the caller holding what the method returned.

43. Do you have to free objects yourself?

Prediction

Java's answer differs from some other languages'.

Predict first

What must a Java programmer do to reclaim an object's memory?

  • Nothing — the system finds unreachable objects and reclaims them automatically
  • Call delete on the object
  • Set every reference to null
  • Call the garbage collector explicitly

Correct: Nothing — the system finds unreachable objects and reclaims them automatically

Why: You do not have to do anything to make garbage collection happen, and in general you do not have to be aware of it. Setting references to null is occasionally useful to make an object unreachable sooner, but it is not required — and there is no delete operator in Java at all.

44. When would you notice garbage collection?

Edge cases

It is meant to be invisible. Sometimes it is not.

Discussion prompt

Think Java says that in high-performance applications you may notice a slight delay now and then. What kind of program would that matter to, and what is the trade being made?

Hint: What if the pause happens at the wrong moment?

Answer:

Anything with a hard timing requirement: a game rendering sixty frames a second, audio processing, or a control system. A pause of a few milliseconds at the wrong moment is visible or audible.

The trade is safety and convenience against predictability. Java removes memory-management bugs entirely and gives up precise control over when memory is reclaimed.

That is why languages requiring manual memory management still exist for operating systems, game engines and embedded control. Neither choice is always right — which is the same conclusion the next section reaches about mutability.

45. Mutable, immutable, and StringBuilder

Section

Sections 10.10-10.11

46. Two designs, each with advantages

Concept

Points and Rectangles are mutable because their attributes can be modified — directly, like box.x = 15, or through methods like box.translate(15, 0). Strings and Integers are immutable: they allow no direct access to their attributes and provide no methods that change them.

Figure (svg): Two boxes comparing the advantages of immutable objects against those of mutable ones

immutable objectsmutable objects
pass them without worrying about side effectssometimes more efficient to modify than to recreate
two equal strings can be stored only oncesome computations are expressed more naturally
easier to debug, more reliableno new object per change

Neither design is always better, which is why you will see both. Knowing which kind of object you hold is what matters.

47. The surprise that immutability makes possible

Notation

Because strings cannot change, the compiler is allowed to share them — with a result that contradicts Lesson 6b's advice.

Annotate

  • Both strings are specified at compile time, so the compiler can tell they are equivalent — it evaluates the concatenation before the program ever runs.
  • Because strings are immutable there is no need to make two copies. The compiler creates one String and makes both variables refer to it.
  • So s1 == s2 is true: they are not just equivalent, they are identical — the same object.
  • This does not make == safe for strings. It works here only because both values are known at compile time; the moment one comes from input or is built at run time, it fails. Lesson 6b's rule stands: always use equals.
  • The real point is the memory saving. Two equal strings can be stored only once, which reduces the memory a program uses and can speed it up.

A rule that works only in the cases you happen to test is worse than no rule. This example explains why == sometimes appears to work, which is exactly the knowledge that stops you relying on it.

48. Where immutability becomes expensive

Worked example

Building a long string by concatenating many small pieces is the case where mutable objects are both more efficient and arguably more natural.

String text = "";
for (int i = 0; i < 10; i++) {
    String line = in.nextLine();      // new string
    text = text + line + '\n';        // two more strings
}
System.out.print("You entered:\n" + text);
per iterationstrings created
in.nextLine()1 — a new string each time
text + line1 more
... + '\n'1 more
per iteration3
over 10 iterations30

Count the objects created in the loop body.

Why: nextLine returns a new string; concatenating creates another; appending the newline creates a third.

Multiply by the iterations.

Why: This loop creates 30 String objects, for a program that looks like it handles ten lines.

Notice what happens to them.

Why: At the end, text refers to the most recent one. Garbage collection deletes the rest — but that is a lot of garbage for a seemingly simple program.

Notice why immutability causes it.

Why: Every concatenation must create a new object, because it cannot modify either of the strings it was given.

Verify: Count for yourself: three objects per iteration, ten iterations.

Why: Now imagine ten thousand lines instead of ten. The cost grows with the number of pieces and with the length of the accumulated text, because each concatenation copies everything so far — which is why this pattern is genuinely slow rather than merely wasteful.

49. How many String objects does this loop create?

Prediction

Count per iteration, then multiply.

String text = "";
for (int i = 0; i < 10; i++) {
    String line = in.nextLine();      // new string
    text = text + line + '\n';        // two more strings
}
System.out.print("You entered:\n" + text);
sourceobjects per iteration
in.nextLine()1
text + line1
... + newline1

Predict first

Over ten iterations, roughly how many String objects are created?

  • 30
  • 10
  • 3
  • 1

Correct: 30

Why: Three per iteration — the line read, the result of concatenating it, and the result of appending the newline — over ten iterations. At the end only one is still referenced and the other 29 become garbage, which is a great deal of allocation for a program that reads ten lines.

50. StringBuilder: a mutable string

Concept

The Java library provides the StringBuilder class for exactly this reason. Because StringBuilder objects are mutable, they can implement concatenation far more efficiently.

StringBuilder text = new StringBuilder();
for (int i = 0; i < 10; i++) {
    String line = in.nextLine();
    text.append(line);
    text.append('\n');
}
System.out.print("You entered:\n" + text);
String versionStringBuilder version
objects created per iteration31 — just the input line
what append doesn/amodifies the StringBuilder in place
objects created by the concatenation200
needs an import?nono — it is in java.lang

The append method takes a String and appends it to the end of the StringBuilder. Each time it is invoked, it modifies the StringBuilder; it does not create any new objects. If you need the result as a String, call toString(). Programs that manipulate large amounts of text run much faster with StringBuilder than with String.

51. Concatenating in a loop

Trap

The trap

Correct, readable, and quadratic. The cost is invisible in the source.

String result = "";
for (int i = 0; i < n; i++) {
    result = result + something;      // copies everything so far
}
nobjects createdcharacters copied
10about 20a few hundred
1,000about 2,000about half a million
100,000about 200,000about five billion

The third row is the problem. Each concatenation copies the whole accumulated string, so the total work grows with the square of the number of pieces — the same shape as the nested loops in Lesson 6a.

The fix

Use a StringBuilder when the number of pieces is large or unknown.

StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) {
    sb.append(something);             // modifies in place
}
String result = sb.toString();
situationuse
joining two or three known piecesString concatenation — clearer
building text in a loopStringBuilder
thousands of piecesStringBuilder, definitely
you need the result as a Stringcall toString() at the end

The first row matters too: do not reach for StringBuilder everywhere. "Total: " + n is clearer than three method calls, and for a couple of pieces the cost is irrelevant. The rule is about loops.

52. String or StringBuilder?

Definition probe

The deciding question is how many pieces, and whether it is in a loop.

Sort into buckets

Sort each situation.

String concatenation
building a message from a label and a value; joining a first and last name
StringBuilder
accumulating a report line by line in a loop; building a string from 100,000 characters
str
A couple of pieces, joined once. Concatenation is clearer and the cost is negligible — reaching for StringBuilder here would add noise.
sb
Many pieces, accumulated in a loop. Each concatenation would copy everything so far, so the total work grows with the square of the number of pieces.

53. Does append create a new object?

Prediction

StringBuilder is mutable.

StringBuilder sb = new StringBuilder();
sb.append("hello");
System.out.println(sb);
String.concatStringBuilder.append
creates a new object?yesno
must you assign the result?yesno

Predict first

Does sb.append("hello") need its return value assigned?

  • No — append modifies the StringBuilder in place
  • Yes — like every String method, it returns a new object
  • Yes, otherwise the StringBuilder is emptied
  • It depends on the length of the string

Correct: No — append modifies the StringBuilder in place

Why: StringBuilder is mutable, so append changes the object it is invoked on and creates nothing new — which is exactly the opposite of s.toUpperCase() from Lesson 9a. This is the single most useful practical consequence of the mutable/immutable distinction: for an immutable object you must assign the result; for a mutable one you must not need to.

54. Mutable against immutable

Trade off

Fill the blanks from Sections 10.10 and 10.11.

Comparison matrix

immutable (String)mutable (StringBuilder)
safe to share between variables?yes — nothing can change itno — a change is visible through every reference
building text in a loopa new object per concatenationmodified in place, no new objects
methods returna new object, which you must assignnothing useful — the object itself changed

Read across the last row: whether you have to assign a method's result is a reliable signal of which kind of object you are holding. It is the practical form of the whole distinction.

55. Three lifetimes

Comparison

This lesson introduced a third clock. Fill the blanks.

Comparison matrix

local variableattributeobject
created whenthe method is invokedthe object is creatednew runs
disappears whenthe method returnsthe object is destroyednothing refers to it any more
visible whereinside its own method onlyin any of the object's methodsanywhere a reference reaches

The bottom-right cell is the one that makes aliasing possible, and the one garbage collection is about: an object's life is decided by references, not by scope.

56. The pattern to carry away

Pattern

Two questions answer most of what this lesson covered, and both are about lifetime.

// how long must this value live?
int oldv = this.x;      // one method call    -> local variable
this.x = newv;          // as long as the object -> attribute

// how many pieces am I joining?
String s = "Total: " + n;              // a couple -> concatenation

StringBuilder sb = new StringBuilder(); // many, in a loop
for (...) { sb.append(piece); }         // -> StringBuilder
String result = sb.toString();
questionthe answer decides
how long must this value live?local variable, or attribute
does anything still refer to this object?whether it is garbage
how many pieces of text am I joining?String, or StringBuilder
must I assign this method's result?whether the object is immutable

57. Check: scope

Check

Work it out before you click.

public void shift(int dx) {
    int temp = this.x;
    this.x = temp + dx;
}
variablekindsurvives the call?
dxparameterno
templocal variableno
this.xattributeyes

Check your understanding

Which of these still exists after the method returns?

  • A. this.x, because attributes belong to the object (correct)
  • B. temp, because it was assigned a value
  • C. dx, because it was a parameter
  • D. All three

Answer: A

Why: Parameters and local variables are created when a method is invoked and disappear when it returns, along with the frame that held them. An attribute belongs to the object rather than to any method, so it lives as long as the object does — which is what allows an object to remember anything between calls.

Why B tempts people
temp is a local variable, discarded with the frame regardless of what it held.
Why C tempts people
A parameter has exactly the same lifetime as a local variable.
Why D tempts people
Only the attribute survives; the other two live and die with the method call.

58. Check: garbage collection

Check

Work it out before you click.

Point a = new Point(1, 2);
Point b = a;
a = null;
afterreferences to the Point
Point b = a;2
a = null;1

Check your understanding

Is the Point eligible for garbage collection?

  • A. No — b still refers to it (correct)
  • B. Yes — a was set to null
  • C. Yes — setting any reference to null deletes the object
  • D. Only after b goes out of scope, which happens immediately

Answer: A

Why: An object is collectable only when nothing refers to it at all. Setting a to null removes one of two references, and b still points at the object — so it remains reachable and its attributes can still be read. Counting references to the object rather than assignments to variables is the reliable method.

Why B tempts people
Setting one variable to null says nothing about other references to the same object.
Why C tempts people
null never deletes anything; it only changes what one variable refers to.
Why D tempts people
b is still in scope and still referring to the object, so nothing has become unreachable.

59. Check: StringBuilder

Check

Work it out before you click.

StringBuilder sb = new StringBuilder();
sb.append("a");
sb.append("b");
System.out.println(sb);
callnew object created?sb holds
append("a")no"a"
append("b")no"ab"

Check your understanding

What is displayed, and how many StringBuilder objects were created?

  • A. ab, and one StringBuilder (correct)
  • B. ab, and three StringBuilders
  • C. an empty line, because the results were not assigned
  • D. b, because each append replaces the contents

Answer: A

Why: StringBuilder is mutable, so append modifies the existing object rather than creating a new one — only the single new StringBuilder() allocates anything. Because the object itself changes, no assignment is needed, which is the opposite of every String method.

Why B tempts people
That would be the String behaviour, where each concatenation creates a new object. Avoiding exactly that is why StringBuilder exists.
Why C tempts people
Assignment is needed for immutable objects; append changes the object in place, so there is nothing to assign.
Why D tempts people
append adds to the end rather than replacing — which is what the name says.

60. Why the same question keeps coming back

Real world

Mutable or immutable is not a Java detail. It is one of the recurring design questions in programming.

Discussion prompt

You have now seen the trade three times: Strings against StringBuilder, arrays against nothing, and Rectangles against a hypothetical immutable version. What is the trade, stated generally — and how would you decide for a class of your own?

Hint: One side buys safety; the other buys efficiency.

Answer:

Immutability buys safety: objects can be shared freely, passed to any method, and used as keys, with no possibility of one holder changing what another sees. Mutability buys efficiency and naturalness: no new object per change, and some operations really are actions on a thing.

The deciding questions are how much the object will be shared, and how often it will change. Something shared widely and changed rarely should be immutable; something private to one part of the program and changed constantly can be mutable.

Modern practice leans toward immutable by default, precisely because the aliasing bugs from Lesson 10a are so hard to find. But Think Java's conclusion is the honest one: neither design is always better, which is why the Java library gives you both String and StringBuilder.

61. How sure are you?

Commit first

Commit to an answer and to your confidence.

Predict first

In String s1 = "Hi, Mom!"; String s2 = "Hi, " + "Mom!"; why is s1 == s2 true?

  • Both values are known at compile time, so the compiler creates one shared String — which is safe only because Strings are immutable
  • Because == always compares the characters of Strings
  • Because concatenation always reuses existing objects
  • It is not true; == is never true for Strings

Correct: Both values are known at compile time, so the compiler creates one shared String — which is safe only because Strings are immutable

Why: The compiler evaluates "Hi, " + "Mom!" before the program runs, sees that the two values are equivalent, and — because a String can never change — creates a single object that both variables refer to. So they are not merely equal but identical. This does not make == safe for Strings: it works here only because both values are compile-time constants, and it fails the moment one comes from input or is built at run time. Lesson 6b's rule stands, and this example explains why the wrong rule sometimes appears to work.

62. Explain it to someone else

Explain it

Two minutes, out loud.

Discussion prompt

A classmate is building a report by concatenating a thousand lines with + in a loop, and cannot understand why it is slow when the code looks so simple. Explain the cost, and explain why StringBuilder does not have the same problem.

Hint: What has to happen on each concatenation, given that Strings are immutable?

Answer:

Strings cannot change, so every + has to build a brand-new string containing everything you had plus the new piece — which means copying the whole accumulated text. By line 1000 each concatenation is copying 999 lines' worth of characters, so the total work grows with the square of the number of lines.

StringBuilder is mutable, so append adds to the end of the existing object without copying what is already there. One object, a thousand appends, no copying.

The general point worth adding: the cost is invisible in the source. Both loops look the same length, and one of them does a thousand times more work — which is the same lesson as the hidden loop inside inRange in Lesson 7b.

63. Exit ticket

Exit ticket

One question before you close the deck.

Predict first

When does an object become eligible for garbage collection?

  • When there are no references to it anywhere in the program
  • When one of its references is set to null
  • When the method that created it returns
  • When the programmer calls delete on it

Correct: When there are no references to it anywhere in the program

Why: If there are no references to an object, there is no way to access its attributes or invoke a method on it — from the program's point of view it has ceased to exist, so its space can be reclaimed. Setting one reference to null is not enough if another still points at it, and an object created in a method survives if the method returns a reference to it. Java has no delete operator at all: you do not have to do anything to make garbage collection happen.

64. Draw the whole lesson

Connect it up

One page, from memory.

Draw it

Draw the UML class diagram for Rectangle, with its four attributes and two methods, marking visibility correctly and using UML's name-colon-type order. Beside it, draw the memory diagram for two Rectangle objects, and mark which diagram would tell you the width of one particular box. Then write the three kinds of variable with when each is created and destroyed. Finally, write the two versions of the ten-line reading loop and count the objects each one creates.

65. Recap

Recap

Six sections that step back from using objects to describing them — how they are documented, drawn, scoped, and reclaimed.

if you remember one thingit is this
about scopelet the lifetime decide where to declare it
about objectsreferences keep an object alive, not scope
about textbuilding it in a loop wants a StringBuilder

Sources

  1. Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 10 (Mutable Objects), Sections 10.6-10.11, pp. 173-179
  2. Java SE 21 API — java.lang.StringBuilder
  3. Think Java 2e — free online edition and source code

Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108