Random Numbers, Histograms, and the Enhanced for Loop

Pseudorandom numbers and why a computer cannot produce real ones, counting values into a histogram, the single-pass trick of using a value as an index, the enhanced for loop, and the doubloon program that pulls the whole chapter together. Follows Think Java 2e, Chapter 7 (Arrays and References), Sections 7.6-7.9, pp. 115-121, cross-referenced against Java SE 21 API — java.util.Random.

Subject: Java · 65 slides · code lesson

Open the interactive version of this deck

What this lesson covers

The lesson, slide by slide

1. Random Numbers, Histograms, and the Enhanced for Loop

Title

Think Java 2e · Chapter 7 · Arrays and References

Sections 7.6-7.9 · pp. 115-121

2. What you will be able to do

Objectives

This lesson follows Think Java 2e, Chapter 7 (Arrays and References), Sections 7.6-7.9, pp. 115-121. Everything on these slides can be checked against those pages.

1. Use java.util.Random to fill an array with pseudorandom values, and say what nextInt(n) returns.

2. Explain what deterministic means and why pseudorandom is not the same as random.

3. Write a counting reduce that counts elements falling in a range.

4. Build a histogram in a single pass by using a value as an index into a counter array.

5. Write an enhanced for loop, and say when it cannot be used.

6. Convert a character to an array index with letter - 'a', and explain why that works.

3. Retrieve before you read

Warm-up

One fact from the previous lesson decides how the histogram is built.

Discussion prompt

What are the elements of new int[100] immediately after it is created? And what are the legal indexes for that array?

Hint: The new operator does something for you.

Answer:

All 100 zeros, because new initialises every element. The legal indexes are 0 to 99.

Both facts matter in this lesson. An array of counters is ready to count the moment it is created — no initialisation loop needed — and the range 0 to 99 happens to be exactly the range of scores the histogram counts. That coincidence is the trick the chapter is building toward.

4. Using a value as an index

Concept

The chapter opened with a problem: counting 26 letters without declaring 26 variables. This lesson finishes it. The key move is to stop thinking of an index as a position and start thinking of it as a name for a counter.

Figure (svg): An array of counters with the element at index 62 highlighted, showing a score of 62 incrementing its own counter

Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 7 (Arrays and References), Sections 7.6-7.9, pp. 115-121 — Sections 7.6-7.9, printed pages 115-121.

5. Random numbers

Section

Section 7.6

6. Computers cannot be truly random

Concept

Most programs do the same thing every time they run; such programs are deterministic. Usually that is a good thing — we expect the same calculation to yield the same result. But games, and scientific simulations, need the computer to be unpredictable.

import java.util.Random;

Random random = new Random();
int n = random.nextInt(100);     // 0 to 99 inclusive
callreturnsrange
random.nextInt(100)a pseudorandom int0 to 99
random.nextInt(6)a pseudorandom int0 to 5
random.nextInt(6) + 1a die roll1 to 6

deterministic — Doing the same thing every time it runs.

pseudorandom — Produced by an algorithm that generates an unpredictable-looking sequence, which is not truly random.

Making a program nondeterministic turns out to be hard, because it is impossible for a computer to generate truly random numbers. But there are algorithms producing unpredictable sequences called pseudorandom numbers, and for most applications they are as good as random.

7. What nextInt(n) actually promises

Notation

The range is the detail people get wrong, and it is the same off-by-one shape as everything else in this chapter.

Annotate

  • nextInt(n) returns a value from 0 to n-1 inclusive. The argument is the number of possible values, not the largest one.
  • That is exactly the range of valid indexes for an array of length n, which is not a coincidence — it is what makes a[random.nextInt(a.length)] pick a random element safely.
  • To shift the range, add. A die is nextInt(6) + 1, because you want six values starting at 1 rather than at 0.
  • Create one Random object and reuse it. Creating a new one inside a loop is a common mistake and can produce repeated values.

If you generate a long series of random numbers, every value should appear approximately the same number of times. Testing that claim is exactly what the histogram in the next section does.

8. Filling an array with random values

Worked example

This method creates an int array and fills it with random numbers between 0 and 99. The argument gives the desired size, and the return value is a reference to the new array.

public static int[] randomArray(int size) {
    Random random = new Random();
    int[] a = new int[size];
    for (int i = 0; i < a.length; i++) {
        a[i] = random.nextInt(100);
    }
    return a;
}
stepwhat happens
Random random = new Random();create one generator, outside the loop
int[] a = new int[size];create the array, all elements 0
a[i] = random.nextInt(100);overwrite each element with a value 0 to 99
return a;return a REFERENCE to the new array

Create the Random object once, before the loop.

Why: One generator produces the whole sequence; creating a new one each pass is both wasteful and can repeat values.

Create the array with the requested size.

Why: new int[size] — the size is a parameter, so the caller decides.

Traverse and assign.

Why: The standard i < a.length traversal from the previous lesson, writing rather than reading.

Return the array.

Why: The return type is int[], and what is actually returned is a reference — the array itself is not copied.

Verify: Call randomArray(8) and print it: you should get something like {15, 62, 46, 74, 67, 52, 51, 10}.

Why: Then run the program again and confirm you get different values. If you got the same eight numbers twice, check that the Random object is created once rather than being re-created with the same seed.

9. What range does nextInt(6) produce?

Prediction

The argument is the number of possible values.

Random random = new Random();
int n = random.nextInt(6);
callsmallestlargesthow many values
nextInt(6)056
nextInt(100)099100

Predict first

What values can n take?

  • 0 through 5
  • 1 through 6
  • 0 through 6
  • 1 through 5

Correct: 0 through 5

Why: nextInt(n) returns a value from 0 to n-1 inclusive, so nextInt(6) gives six possible values starting at zero. That range is exactly the set of valid indexes for an array of length 6, which is what makes a[random.nextInt(a.length)] a safe way to pick a random element.

10. Returning an array from a method

Concept

randomArray is the first method in the book to return an array, and it is worth being precise about what crosses the boundary.

Figure (svg): A variable in main with an arrow to the array that randomArray created and returned

The array was created inside the method, yet it survives the method returning — unlike the local variables in Lesson 4a, which vanish with their frame. What vanishes is the method's variable a; the array itself lives on because the caller now holds a reference to it. Chapter 10's garbage collection section explains when it is finally cleaned up.

11. Creating a new Random inside the loop

Trap

The trap

A fresh generator every pass, which can produce repeated values.

int[] a = new int[size];
for (int i = 0; i < a.length; i++) {
    Random random = new Random();     // inside the loop
    a[i] = random.nextInt(100);
}
passwhat is createdrisk
1a new generatorseeded from the clock
2another new generatorthe clock may not have ticked
3anothersame seed, same first value

A Random created with no argument is seeded from the system clock. Several created in quick succession can share a seed and therefore produce the same first value — so the array fills with repeats.

The fix

One generator, created before the loop.

public static int[] randomArray(int size) {
    Random random = new Random();
    int[] a = new int[size];
    for (int i = 0; i < a.length; i++) {
        a[i] = random.nextInt(100);
    }
    return a;
}
createdproduces
one Random, before the loopa single sequence, each call continuing it
a new Random each passseveral sequences that may all start the same

This is the same principle as the accumulator in Lesson 7a: anything that must carry state across passes belongs outside the loop. A random generator's state is exactly what makes each call give a different answer.

12. Roll a die

Fill the middle

Produce a value from 1 to 6.

Fill in the blanks

int roll = random.nextInt(6) + 1;

Why: nextInt(6) gives six values, 0 through 5, and adding 1 shifts them to 1 through 6. Writing nextInt(7) would give seven possible values including 0 and 6, which is a seven-sided die with a zero on it — the classic off-by-one in random number generation.

13. Why can't a computer be truly random?

Socratic

Think Java says it is impossible. Work out why.

Discussion prompt

A program is a sequence of instructions that produces the same result from the same inputs. Given that, why can no algorithm produce truly random numbers — and where could genuine randomness come from?

Hint: What would the algorithm have to do differently on two runs with the same starting state?

Answer:

An algorithm is deterministic by definition: same instructions, same state, same result. So any sequence it produces is completely determined by its starting state — the seed — and is reproducible by anyone who knows it.

Genuine randomness has to come from outside the computation: physical noise, timing jitter, radioactive decay. Operating systems collect such sources for cryptographic use, where predictability would be a security hole.

For games and simulations, pseudorandom is genuinely as good as random — the sequence passes statistical tests and nobody is trying to predict it. Knowing the difference matters when someone is: never use java.util.Random for anything security-related.

14. Where does each declaration belong?

Definition probe

Inside the loop, or outside it?

Sort into buckets

Sort each declaration for a method that fills an array with random values.

outside the loop
the Random object; the array being filled; the loop index i; an accumulator summing the values as they are generated
inside the loop
a temporary holding one generated value
out
It must survive across passes — the generator carries its own state, the array is being filled over many passes, the index controls the loop, and the accumulator builds up a total.
in
It is used and finished within a single pass, so recreating it each time costs nothing and keeps its scope tight.

15. Counting in ranges

Section

Section 7.7

16. A histogram is a set of counters

Concept

If those random values were exam scores, a teacher might present them as a histogram — in statistics, a set of counters that keeps track of the number of times each value appears.

public static int inRange(int[] a, int low, int high) {
    int count = 0;
    for (int i = 0; i < a.length; i++) {
        if (a[i] >= low && a[i] < high) {
            count++;
        }
    }
    return count;
}
partrole
int count = 0;the accumulator, declared before the loop
a[i] >= lowlow is INCLUDED in the range
a[i] < highhigh is EXCLUDED
count++one more element in range
return count;the reduced value

This pattern should look familiar: it is another reduce operation from Lesson 7a. Notice that low is included with >= but high is excluded with < — this design keeps us from counting any score twice.

17. Why the ranges must not overlap

Picture it

Half-open ranges — include the bottom, exclude the top — are the same convention as substring in Lesson 6b, and they exist for the same reason.

Figure (svg): Two panels contrasting overlapping grade ranges that double-count with half-open ranges that tile exactly

With half-open ranges the boundaries line up: the top of one range is the bottom of the next, so the ranges tile the whole scale with no gaps and nothing counted twice.

18. Counting the grade ranges

Worked example

With inRange written, counting the five grade bands is five calls. This code is repetitive, and acceptable as long as the number of ranges is small.

int[] scores = randomArray(30);
int a = inRange(scores, 90, 100);
int b = inRange(scores, 80, 90);
int c = inRange(scores, 70, 80);
int d = inRange(scores, 60, 70);
int f = inRange(scores, 0, 60);
callcounts scorestraversals of the array
inRange(scores, 90, 100)90 to 991
inRange(scores, 80, 90)80 to 891
inRange(scores, 70, 80)70 to 791
inRange(scores, 60, 70)60 to 691
inRange(scores, 0, 60)0 to 591
totalevery score exactly once5

Write one call per band.

Why: Each returns the count for that band.

Check the boundaries line up.

Why: 90-100, 80-90, 70-80: the top of each is the bottom of the next, so nothing is missed or double counted.

Notice the cost.

Why: Each call traverses the entire array, so five bands means five full traversals.

Ask what happens with 100 bands.

Why: One hundred calls, and one hundred traversals. That is the problem the next idea solves.

Verify: Add the five counts together; the total must equal scores.length, which is 30.

Why: That check is worth doing every time you partition data into bands: if the total does not match, either a range is missing values or two ranges overlap.

19. Which scores does inRange(a, 70, 80) count?

Prediction

Low is included, high is excluded.

if (a[i] >= low && a[i] < high) {
    count++;
}
scorecounted?
69no — below the low end
70yes — low is included
79yes
80no — high is excluded

Predict first

Which range of scores is counted?

  • 70 to 79
  • 70 to 80
  • 71 to 80
  • 71 to 79

Correct: 70 to 79

Why: The condition uses >= low so 70 is included, and < high so 80 is not. This half-open design is what lets the next band start at 80 without double-counting — the top of one range is exactly the bottom of the next.

20. The version that does not scale

Concept

Suppose you wanted to track how many times each individual score appears. With the inRange approach you would have to write 100 lines of code.

int count0 = inRange(scores, 0, 1);
int count1 = inRange(scores, 1, 2);
int count2 = inRange(scores, 2, 3);
// ...
int count99 = inRange(scores, 99, 100);
problemconsequence
100 separate variablesyou cannot loop over them
100 lines of near-identical codeany change must be made 100 times
100 traversals of the scores arrayslow, and unnecessarily so

What we need is a way to store 100 counters, preferably so we can use an index to access them. As Think Java puts it: wait a minute — that is exactly what an array does. This is the same realisation the chapter opened with, about 26 letter counters.

21. Ranges that overlap or leave gaps

Trap

The trap

Inclusive at both ends. Every boundary score is counted twice.

// counting with <= at both ends
if (a[i] >= 80 && a[i] <= 90) countB++;
if (a[i] >= 90 && a[i] <= 100) countA++;
scorecounted in B?counted in A?total
85yesnoonce — correct
90yesyesTWICE
95noyesonce — correct

The counts add up to more than the number of scores, which is the symptom. Only the boundary values are wrong, so a test with no score exactly on a boundary would pass.

The fix

Half-open: include the low end, exclude the high.

if (a[i] >= low && a[i] < high) {
    count++;
}
scorein [80, 90)?in [90, 100)?total
85yesnoonce
90noyesonce
95noyesonce

This is the third appearance of the half-open convention: array indexes 0 <= i < length, substring(start, end), and now counting ranges. Each time, the reason is the same — adjacent ranges tile exactly, with no gap and no overlap.

22. Which traversal pattern is inRange?

Definition probe

The three shapes from Lesson 7a.

Sort into buckets

Sort each method by its pattern.

reduce
inRange — counts elements in a band; sum — adds every element
search
search — returns an index or -1
transform or fill
randomArray — fills every element
r
Many values are combined into one, using an accumulator declared before the loop. Counting is just summing ones.
s
The result is a position rather than a combined value, and the loop returns early on a match.
t
Every element is written, and the array itself is the result.

23. How many traversals for 100 counters?

Estimation

Each call to inRange walks the whole array.

Predict first

Using inRange to count each of 100 possible scores separately, how many times is the scores array traversed?

  • 100
  • 1
  • 2
  • It depends on the scores

Correct: 100

Why: Every call to inRange contains its own loop over the entire array, so 100 calls means 100 full traversals — and the array is unchanged between them, so 99 of those are doing work that could have been done in the first. That inefficiency is precisely what the single-pass version in the next idea eliminates.

24. Why does the check 'counts add to length' work?

Explain it to yourself

A cheap test that catches both kinds of range error.

Discussion prompt

After partitioning scores into bands, adding the counts should give the total number of scores. Explain what a total that is too high tells you, and what a total that is too low tells you.

Hint: Two different mistakes, two different symptoms.

Answer:

Too high means overlap: some value fell into two bands and was counted twice. Too low means a gap: some value fell into no band at all and was counted zero times.

The check is valuable because it is cheap and it catches boundary errors, which are exactly the ones that survive casual testing — a test with no value on a boundary passes even when the ranges are wrong. Any time you partition data, add the parts up.

25. The histogram in one pass

Section

Section 7.7, continued

26. Use the value itself as the index

Concept

The inRange version works but is not as efficient as it could be. For each score, we already know which range it falls in — the score itself. So we can use that value to increment the corresponding counter directly.

int[] counts = new int[100];
for (int i = 0; i < scores.length; i++) {
    int index = scores[i];
    counts[index]++;
}
scores[i]index usedeffect
6262counts[62]++
1515counts[15]++
6262counts[62]++ again — now 2
9999counts[99]++

Each pass selects one element from scores and uses it as an index to increment the corresponding element of counts. Because this traverses the scores array only once, it is much more efficient.

27. A value pointing at its own counter

Picture it

This is the conceptual jump of the chapter. An index has been a position until now; here it is a name, and the value chooses which counter to bump.

Figure (svg): A score of 62 shown selecting the counter at index 62 in a counts array

It works because the counts array was made with exactly as many elements as there are possible values — new int[100] for scores 0 to 99. The value and the index range line up by construction.

28. Comparing the two versions

Worked example

Both produce the same histogram. The difference is how much work they do, and it is not a small difference.

// version 1: correct, but 100 traversals
int[] counts = new int[100];
for (int i = 0; i < counts.length; i++) {
    counts[i] = inRange(scores, i, i + 1);
}

// version 2: one traversal
int[] counts = new int[100];
for (int i = 0; i < scores.length; i++) {
    counts[scores[i]]++;
}
versionloops overtraversals of scorestotal steps for 30 scores
1 — inRangecounts (100)100about 3,000
2 — value as indexscores (30)130

Look at what version 1 loops over.

Why: The counts array — one pass per counter, and each pass calls inRange which walks all the scores.

Look at what version 2 loops over.

Why: The scores array — one pass total, each score bumping one counter.

Notice the loop variable's meaning changed.

Why: In version 1, i is a score value being asked about. In version 2, i is a position in scores, and the value found there is the index.

Count the steps.

Why: Version 1 does scores.length times counts.length work; version 2 does scores.length. For 30 scores that is 3,000 against 30.

Verify: Run both and confirm the counts arrays are identical — Arrays.equals(a, b) will tell you in one line.

Why: Same answer, a hundredth of the work. This is the first place in the book where two correct programs differ enormously in cost, and it is the idea Chapter 12 develops when it compares sequential and binary search.

29. What does counts[62] hold?

Prediction

The single-pass histogram, run on four scores.

int[] scores = {62, 15, 62, 99};
int[] counts = new int[100];
for (int i = 0; i < scores.length; i++) {
    counts[scores[i]]++;
}
scorecounter incrementedcounts[62] after
62counts[62]1
15counts[15]1
62counts[62]2
99counts[99]2

Predict first

What is counts[62]?

  • 2
  • 1
  • 62
  • 4

Correct: 2

Why: The value 62 appears twice in scores, and each occurrence increments the counter at index 62, so it ends at 2. The counter's index is the score it counts — that correspondence is what makes the whole technique work.

30. Using the loop variable three ways

Concept

Version 1 is worth one more look, because it uses the same variable for three different jobs in three consecutive positions.

for (int i = 0; i < counts.length; i++) {
    counts[i] = inRange(scores, i, i + 1);
}
where i appearswhat it means there
counts[i]an index into the counts array
inRange(scores, i, ...)the low end of the range — a score value
..., i + 1)the high end — one more than that score

Think Java points this out explicitly: notice that we are using the loop variable i three times. It works because the counter at index i counts exactly the score i — the correspondence between index and value is the whole design. Version 2 exploits that correspondence directly instead of restating it.

31. A value outside the counter array's range

Trap

The trap

The value is used as an index without checking it fits.

int[] counts = new int[100];
for (int i = 0; i < scores.length; i++) {
    counts[scores[i]]++;        // what if a score is 100? or -1?
}
a score ofindex usedoutcome
00fine
9999fine — the last counter
100100ArrayIndexOutOfBoundsException
-1-1ArrayIndexOutOfBoundsException

The technique depends entirely on every value being a legal index. randomArray guarantees 0 to 99, so this code is safe for that data — but it would break instantly on scores read from a file.

The fix

Size the counter array to the range of possible values, and validate anything from outside.

int[] counts = new int[100];          // one counter per possible score
for (int score : scores) {
    if (score >= 0 && score < counts.length) {
        counts[score]++;
    } else {
        System.err.println("out of range: " + score);
    }
}
guarantee neededhow it is met
values are 0 or morenextInt never returns negative — or check it
values are below counts.lengthnextInt(100) with new int[100] — or check it
data from outside the programmust be validated, as in Lesson 5b

The design rule: the counter array's length must be the number of possible values. When you control the data that is a matter of construction; when you do not, it is a guard clause.

32. Which array does the loop go over?

Discrimination

The two versions loop over different arrays, and that is the whole difference.

Sort into buckets

Sort each loop header by which array it traverses.

traverses counts — slow
for (int i = 0; i < counts.length; i++) with inRange inside
traverses scores — one pass
for (int i = 0; i < scores.length; i++) with counts[scores[i]]++; for (int score : scores) with counts[score]++
cnt
It asks, for each possible value, how many scores match — and answering each question requires walking the whole scores array again.
scr
It asks, for each score, which counter to bump. One pass over the data, and every score is handled as it is met.

33. How does the cost grow?

Scale up

Compare the two versions as the amount of data increases.

Step through it

If the number of scores doubles, what happens to each version's cost?

  1. Version 1 does about 3,000 steps; version 2 does 30. Both are instant.
  2. Version 1 does about 300,000; version 2 does 3,000. Version 1 is now noticeably slower.
  3. Version 1 does about 300 million; version 2 does 3 million. Version 1 has become unusable while version 2 is still fast.

Both double — the counter array's size is fixed, so version 1's cost is scores times 100 and version 2's is scores. Version 1 is a hundred times slower at every size, which is a constant factor rather than a different growth rate. Constant factors of 100 still decide whether a program is usable.

34. Where else is a value used as an index?

Real world

This technique — let the data choose the slot — is far more general than histograms.

Discussion prompt

Counting scores works because a score is already a number in the right range. Where else could you use a value directly as an index, and what would you do when the values are not numbers at all?

Hint: The next idea converts letters into indexes.

Answer:

Anywhere the values form a small known range: counting die rolls, tallying votes for numbered candidates, counting how many items fall in each month of the year.

When the values are not numbers, you map them to numbers. The next section does exactly that with letter - 'a', turning 26 letters into indexes 0 to 25.

And when the range is huge or unknown — arbitrary words, say — an array of counters stops being practical, and you need a different structure. That structure is a hash map, and it is doing conceptually the same thing: computing an index from a value.

35. The enhanced for loop

Section

Section 7.8

36. For each value in values

Concept

Since traversing arrays is so common, Java provides an alternative syntax that makes the code more compact. It is called the enhanced for loop, also known as the for each loop.

// the standard form
for (int i = 0; i < values.length; i++) {
    int value = values[i];
    System.out.println(value);
}

// the enhanced form
for (int value : values) {
    System.out.println(value);
}
partmeaning
int valuea variable that takes each element in turn
:read as "in"
valuesthe array to traverse
the whole header"for each value in values"

The single line for (int value : values) replaces the first two lines of the standard form. It hides the details of iterating each index, and focuses on the values themselves.

37. Naming, and what is hidden

Notation

The convention makes the loop read as English, and what the form leaves out is exactly what decides when you can use it.

Annotate

  • It is conventional to use plural nouns for array variables and singular nouns for element variables, which makes the header read as a sentence: for each score in scores.
  • The index is gone. There is no i, which is what makes the loop shorter and also what limits it.
  • The bounds are gone too. You cannot get i < length wrong, because you do not write it — a whole class of off-by-one error disappears.
  • The direction is fixed. It always goes forward, through every element. There is no way to go backwards, skip elements, or stop early by index.
  • The element variable is a copy. Assigning to it does not change the array, which is the trap on a later slide.

Enhanced for loops often make the code more readable, especially for accumulating values. The question to ask before using one is simply: do I need the index?

38. Rewriting the histogram

Worked example

Using the enhanced for loop and removing the temporary variable, the histogram code becomes about as short as it can be.

// before
int[] counts = new int[100];
for (int i = 0; i < scores.length; i++) {
    int index = scores[i];
    counts[index]++;
}

// after
int[] counts = new int[100];
for (int score : scores) {
    counts[score]++;
}
linebeforeafter
loop headerindex, condition, updateone clause
temporaryint index = scores[i];the loop variable IS the value
the workcounts[index]++;counts[score]++;
total lines in the body21

Replace the header with the enhanced form.

Why: for (int score : scores) — the variable now holds the element rather than its position.

Delete the temporary.

Why: int index = scores[i]; existed only to name the element, and the loop variable already does.

Note that counts is still indexed normally.

Why: The enhanced loop traverses scores; counts is being indexed by value, which is unaffected.

Check it still reads correctly.

Why: For each score in scores, increment the counter for that score. The code now says what the sentence says.

Verify: Run both versions on the same scores and compare the counts arrays with Arrays.equals.

Why: Identical. The enhanced loop changed the notation, not the algorithm — and notice that the version that is easier to read is also the one where the off-by-one cannot happen.

39. What is the array after this loop?

Prediction

The loop variable is assigned to.

int[] a = {1, 2, 3};
for (int value : a) {
    value = 0;
}
what is assignedeffect on a
the loop variablenone — it is a copy of the element
a[i]would change the array — but is not written here

Predict first

What does a contain afterwards?

  • {1, 2, 3} — unchanged
  • {0, 0, 0}
  • {0, 2, 3}
  • a compile error

Correct: {1, 2, 3} — unchanged

Why: The enhanced for loop assigns a copy of each element to the loop variable, so writing to that variable changes the copy and not the array. This is the same copying that happens in parameter passing, and it is why the enhanced form can read an array but never modify it.

40. When you cannot use it

Concept

Enhanced for loops are not helpful when you need to refer to the index, as in search operations.

for (double d : array) {
    if (d == target) {
        // array contains d, but we don't know where
    }
}
you need toenhanced for?why
visit every element and accumulateyesthe index is never used
return the position of a matchnothere is no index to return
modify the array's elementsnothe loop variable is a copy
traverse backwardsnoit always goes forwards
compare a[i] with a[i + 1]noyou need two positions at once

The first row is the common case, which is why the enhanced form is worth reaching for by default. The remaining four are exactly the situations where the standard form is not merely preferable but necessary.

41. Assigning to the loop variable

Trap

The trap

The array is unchanged. The loop variable holds a copy.

int[] a = {1, 2, 3};
for (int value : a) {
    value = value * 2;        // changes the copy only
}
// a is still {1, 2, 3}
passvaluea[i]
11, then 21 — unchanged
22, then 42 — unchanged
33, then 63 — unchanged

This is Lesson 4a's parameter passing all over again: the loop variable is assigned a copy of each element, so changing it leaves the array alone. It compiles and silently does nothing.

The fix

To modify elements, use the standard form and assign through the index.

int[] a = {1, 2, 3};
for (int i = 0; i < a.length; i++) {
    a[i] = a[i] * 2;          // writes into the array
}
// a is now {2, 4, 6}
formreads elements?can write them?
enhanced foryesno — the variable is a copy
standard foryesyes — a[i] on the left of an assignment

The rule of thumb: enhanced for to read, standard for to write. The transform pattern from Lesson 7a always needs the standard form, for exactly this reason.

42. Enhanced for, or standard for?

Definition probe

Ask whether you need the index.

Sort into buckets

Sort each task.

enhanced for
sum every element; count how many elements are negative; print every element on its own line
standard for
find the index of the largest element; double every element in place
enh
Only the values are needed — the array is read and never written, and no position has to be reported. The enhanced form is shorter and removes the chance of an off-by-one.
std
Either the index itself is part of the answer, or elements must be written. The enhanced form's loop variable is a copy and carries no position, so neither is possible.

43. Write an enhanced for loop

Fill the middle

Sum an int array.

Fill in the blanks

int total = 0;
for (int n : numbers) +=} n;
}

Why: The colon is read as "in", so the header says for each n in numbers. The accumulator is still declared before the loop and updated with += — the enhanced form changes only how you get at each element, not the reduce pattern itself.

44. Could the enhanced loop have allowed writing?

Counterexample

It is a deliberate design decision. Ask what it prevents.

Discussion prompt

Java could have made the enhanced for loop's variable write through to the array. It does not. What would be ambiguous or dangerous if it did — and what does the current design let you assume when you read one?

Hint: Think about what you know the moment you see the enhanced form.

Answer:

The current design means seeing an enhanced for loop tells you the array is not being modified. That is real information for a reader, and it disappears if the variable could write.

It also keeps the form consistent: the enhanced loop works over things other than arrays, including collections where 'writing to the current element' may have no meaning at all.

So the restriction is not an oversight but a guarantee. The shorter form is also the one that promises less — and when you need the guarantee to be weaker, the standard form is there.

45. Counting characters

Section

Section 7.9

46. Turning a letter into an index

Concept

Now the problem the chapter opened with. A word is a doubloon if every letter that appears in it appears exactly twice. The plan is an array of 26 counters — and the missing piece is how to get from a letter to an index.

int[] counts = new int[26];
String lower = s.toLowerCase();

for (int i = 0; i < lower.length(); i++) {
    char letter = lower.charAt(i);
    int index = letter - 'a';
    counts[index]++;
}
letterletter - 'a'counter incremented
'a'0counts[0]
'b'1counts[1]
'm'12counts[12]
'z'25counts[25]

Java allows you to perform arithmetic on characters. Subtracting 'a' from a lowercase letter gives its position in the alphabet: if letter is 'a' the index is 0, if 'b' then 1, and so on.

47. Why subtracting 'a' works

Picture it

It looks like a trick and it is arithmetic. Lesson 6a's point that a char is a number underneath is what makes it possible.

Figure (svg): A row showing letters a through e with their Unicode code points and the result of subtracting the code point of a

This works because the lowercase letters are consecutive in Unicode — the same property that let for (char c = 'a'; c <= 'z'; c++) walk the alphabet in Lesson 6a. It is a fact about the encoding, not a general truth about alphabets.

48. The complete doubloon method

Worked example

Two traversals: one to build the histogram, one to check it. Neither is complicated, and together they solve the problem the chapter opened with in a single pass over the word.

public class Doubloon {

    public static boolean isDoubloon(String s) {
        // count the number of times each letter appears
        int[] counts = new int[26];
        String lower = s.toLowerCase();
        for (char letter : lower.toCharArray()) {
            int index = letter - 'a';
            counts[index]++;
        }

        // determine whether the given word is a doubloon
        for (int count : counts) {
            if (count != 0 && count != 2) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        System.out.println(isDoubloon("Mama"));    // true
        System.out.println(isDoubloon("Lama"));    // false
    }
}
wordcounts after the first loopverdict
"Mama"m appears 2, a appears 2, rest 0true
"Lama"l appears 1, a appears 2, m appears 1false
""all zerostrue — vacuously

Lowercase the string first.

Why: toLowerCase so that 'A' and 'a' count as the same letter — and so that letter - 'a' is in range.

Build the histogram with an enhanced for loop.

Why: lower.toCharArray() is needed because the enhanced form does not work directly on a String.

Check every counter.

Why: If a count is neither 0 nor 2, the word is not a doubloon and we can return immediately.

Return true if the second loop finishes.

Why: Reaching the end means every count was 0 or 2 — which is the definition.

Verify: Expect isDoubloon("Mama") to be true and isDoubloon("Lama") to be false.

Why: Then trace "Mama": lowercased it is "mama", so m appears twice and a appears twice, every other counter is 0, and every count is 0 or 2. For "lama" the l and the m each appear once, so the first non-conforming count returns false.

This example uses methods, if statements, for loops, arithmetic and logical operators, integers, characters, strings, booleans and arrays. It is worth a moment to notice how much that is.

49. What index does 'd' give?

Prediction

Character arithmetic.

char letter = 'd';
int index = letter - 'a';
charactercode point
'a'97
'd'100
100 - 973

Predict first

What is index?

  • 3
  • 4
  • 100
  • 'd'

Correct: 3

Why: Subtracting the code point of 'a' from that of 'd' gives 3, which is d's zero-based position in the alphabet — a is 0, b is 1, c is 2, d is 3. The subtraction works because the lowercase letters occupy consecutive code points in Unicode.

50. Two early returns, doing different jobs

Concept

The checking loop returns from inside the loop and again after it. Both are deliberate, and the pattern is the same as search in Lesson 7a.

for (int count : counts) {
    if (count != 0 && count != 2) {
        return false;      // one bad count is enough
    }
}
return true;               // every count was fine
situationwhich return runswhy
a count is 1the one inside the loopone counterexample settles it
a count is 3the one inside the loopsame
every count is 0 or 2the one after the loopthe loop finished without finding a problem

The asymmetry is worth naming: to prove false you need one counterexample; to prove true you must check everything. That is why the false case can return early and the true case cannot — and it is the same shape as returning -1 only after a search loop completes.

51. Forgetting to lowercase first

Trap

The trap

An uppercase letter produces a negative index.

int[] counts = new int[26];
for (char letter : s.toCharArray()) {    // no toLowerCase
    int index = letter - 'a';
    counts[index]++;
}
lettercode pointletter - 'a'outcome
'a'970fine
'M'77-20ArrayIndexOutOfBoundsException
'Z'90-7ArrayIndexOutOfBoundsException

Uppercase letters come before lowercase in Unicode, so subtracting 'a' from one gives a negative number. isDoubloon("Mama") would throw on its very first character.

The fix

Lowercase the whole string first, which handles case-insensitivity and the range at once.

String lower = s.toLowerCase();
for (char letter : lower.toCharArray()) {
    int index = letter - 'a';
    counts[index]++;
}
what toLowerCase fixeshow
'M' and 'm' should be the same letterboth become 'm'
'M' - 'a' would be negative'm' - 'a' is 12
every index is 0 to 25because every letter is now lowercase

One call solves two problems, which is why the book's instructions say to ignore case, invoke the toLowerCase method before checking. Note that a string containing a space or a digit would still produce an out-of-range index — the method assumes letters only.

52. Is 'Mama' a doubloon?

Prediction

Lowercase it first, then count.

isDoubloon("Mama")
lettercount
m2
a2
every other letter0

Predict first

What does the method return?

  • true — every letter that appears appears exactly twice
  • false — the M is uppercase
  • false — there are only two distinct letters
  • an exception is thrown

Correct: true — every letter that appears appears exactly twice

Why: Lowercased, "Mama" becomes "mama": m appears twice and a appears twice, and every other counter is 0. The check accepts counts of 0 or 2, so all 26 counters pass and the method returns true. The uppercase M causes no problem precisely because toLowerCase runs first.

53. Annotate the doubloon method

Error analysis

Five decisions in fifteen lines, each with a reason.

Annotate

  • new int[26] — one counter per letter of the alphabet, and every element starts at 0, so no initialisation loop is needed.
  • toLowerCase() — does two jobs: it makes the check case-insensitive as the problem requires, and it guarantees every index is 0 to 25.
  • toCharArray() — the enhanced for loop does not work on a String, so the string is converted to an array of characters first.
  • letter - 'a' — character arithmetic, mapping each letter to its alphabet position. This is the step that lets a value choose its own counter.
  • count != 0 && count != 2 — a letter may be absent (0) or appear twice (2); anything else disqualifies the word. Note that 0 must be allowed, since 24 of the 26 counters are usually zero.
  • return false inside, return true after — one bad count settles it immediately, but confirming a doubloon requires checking every counter.

Nothing here is advanced. What makes it satisfying is that it is the whole chapter — arrays, traversal, using a value as an index, and the enhanced for loop — solving a problem that was impractical at the start of it.

54. What does isDoubloon return for an empty string?

Edge cases

Test the method at its edges.

Discussion prompt

What does isDoubloon("") return? And what would happen with isDoubloon("hello world")? Reason from the code rather than from intuition.

Hint: For the second one, work out what ' ' - 'a' is.

Answer:

The empty string returns true. The first loop runs zero times, so every counter stays 0, and the check accepts 0 — so every counter passes. Whether vacuously true is the right answer is a question about the specification rather than the code.

"hello world" throws. The space has code point 32, so ' ' - 'a' is -65, and counts[-65]++ is an ArrayIndexOutOfBoundsException.

So the method silently assumes its input is letters only. That assumption is fine for the exercise and would be a bug in anything handling real text — which is the same lesson as Lesson 5b's validation: a method that works on the data you tested is not the same as a method that is correct.

55. The two histogram versions

Comparison

Same output, very different cost. Fill the blanks.

Comparison matrix

with inRangevalue as index
the loop goes overthe counts arraythe scores array
traversals of scoresone per counter — 100one, total
the loop variable isa score value being asked abouta position in scores
steps for 30 scoresabout 3,00030

Both are correct. The second is a hundred times cheaper because it stops asking how many scores equal this? and starts saying this score belongs here.

56. The pattern to carry away

Pattern

Counting anything into buckets is the same four lines, whatever the values are — as long as you can turn a value into an index.

// 1. one counter per possible value
int[] counts = new int[NUMBER_OF_POSSIBLE_VALUES];

// 2. one pass over the data
for (int value : data) {
    counts[value]++;          // the value names its own counter
}

// for letters, map them into range first:
for (char letter : s.toLowerCase().toCharArray()) {
    counts[letter - 'a']++;
}
requirementwhy
the counter array's length is the number of possible valuesso every value is a legal index
every value is 0 or more and below that lengthotherwise ArrayIndexOutOfBoundsException
new int[] initialises to zeroso the counters are ready without a setup loop
one pass, not one per counterthe value tells you which counter, so no searching is needed

57. Check: nextInt's range

Check

Work it out before you click.

Check your understanding

Which expression produces a random value from 1 to 10 inclusive?

  • A. random.nextInt(10) + 1 (correct)
  • B. random.nextInt(11)
  • C. random.nextInt(1, 10)
  • D. random.nextInt(10)

Answer: A

Why: nextInt(10) gives ten possible values, 0 through 9, and adding 1 shifts them to 1 through 10. The argument is the number of possible values rather than the maximum, which is the same convention as an array's length.

Why B tempts people
This gives eleven values, 0 through 10 — one too many, and it includes 0.
Why C tempts people
The single-argument form is what the book uses; this two-argument version exists in recent Java but is not what Think Java introduces.
Why D tempts people
This gives 0 through 9, which is ten values but starting in the wrong place.

58. Check: the enhanced for loop

Check

Work it out before you click.

int[] a = {5, 10, 15};
int total = 0;
for (int n : a) {
    total += n;
    n = 0;
}
passntotal aftera[i]
1555 — unchanged
2101510 — unchanged
3153015 — unchanged

Check your understanding

What is total, and what does a contain afterwards?

  • A. total is 30 and a is unchanged (correct)
  • B. total is 30 and a is {0, 0, 0}
  • C. total is 0 and a is {0, 0, 0}
  • D. total is 5 and a is unchanged

Answer: A

Why: The accumulator adds each element as it is visited, giving 30. Assigning to the loop variable changes only the copy it holds, so the array is untouched — the enhanced for loop can read an array but never modify it.

Why B tempts people
This assumes the loop variable writes through to the array, which is exactly what the enhanced form does not do.
Why C tempts people
The additions happen before the assignment on each pass, and in any case the assignment affects nothing.
Why D tempts people
The loop runs for all three elements, not just the first.

59. Check: character arithmetic

Check

Work it out before you click.

int[] counts = new int[26];
String lower = "bee".toLowerCase();
for (char letter : lower.toCharArray()) {
    counts[letter - 'a']++;
}
letterindexcounter
b1counts[1] becomes 1
e4counts[4] becomes 1
e4counts[4] becomes 2

Check your understanding

What are counts[1] and counts[4]?

  • A. counts[1] is 1 and counts[4] is 2 (correct)
  • B. counts[1] is 2 and counts[4] is 1
  • C. both are 1
  • D. counts[2] is 1 and counts[5] is 2

Answer: A

Why: 'b' - 'a' is 1 and 'e' - 'a' is 4, so the b increments counter 1 once and the two e's increment counter 4 twice. The mapping is zero-based: a is 0, b is 1, and so on up to z at 25.

Why B tempts people
This reverses the two letters — b appears once in 'bee' and e appears twice.
Why C tempts people
The letter e appears twice, so its counter reaches 2.
Why D tempts people
This uses a one-based mapping, but subtracting 'a' gives 0 for 'a' itself, making the scheme zero-based.

60. Counting with an array is everywhere

Real world

The histogram technique — one counter per possible value, one pass over the data — is one of the most reused ideas in programming.

Discussion prompt

You have used it for exam scores and for letters. Where else would this exact technique work, and what property must the data have for it to be possible at all?

Hint: Think about what the counter array's length has to be.

Answer:

It works wherever the values come from a small, known, countable range: die rolls, days of the week, pixel brightness values 0 to 255, DNA bases, votes for numbered candidates.

The required property is that you can map each value to an index in a fixed range without gaps. Scores are already indexes; letters need - 'a'; anything else needs a mapping you invent.

When the range is huge or unknown — arbitrary words, or user IDs — an array of counters becomes impractical and you reach for a hash map instead. But a hash map is doing the same thing underneath: computing an index from a value. Understanding the array version is what makes the map version make sense.

61. How sure are you?

Commit first

Commit to an answer and to your confidence.

Predict first

Can an enhanced for loop be used to double every element of an array?

  • No — the loop variable is a copy, so assigning to it does not change the array
  • Yes — assigning to the loop variable writes through to the array
  • Yes, but only for arrays of primitives
  • No — the enhanced for loop cannot be used on arrays at all

Correct: No — the loop variable is a copy, so assigning to it does not change the array

Why: The enhanced for loop assigns a copy of each element to the loop variable, exactly as parameter passing copies an argument in Lesson 4a. Writing to that variable changes the copy and the array is untouched — and the code compiles and silently does nothing, which makes it a logic error. To modify elements you need the standard form and a[i] on the left of an assignment. The rule worth keeping: enhanced for to read, standard for to write.

62. Explain it to someone else

Explain it

Two minutes, out loud.

Discussion prompt

Explain to a classmate why counting scores with counts[scores[i]]++ is so much faster than calling inRange a hundred times. They will say both loops look about the same length. Answer that directly.

Hint: Count what is nested inside what.

Answer:

They look the same because you can only see the outer loop. The inRange version has a second loop hidden inside the method — every call walks the entire scores array. So a hundred calls means a hundred full traversals.

The other version has no hidden loop. It walks the scores once, and each score bumps one counter directly, because the score IS the index. There is nothing to search for.

The general lesson worth adding: a method call can hide a loop. Judging cost by the code you can see is exactly how this mistake gets made, and it is why Chapter 12 spends time on counting steps properly.

63. Exit ticket

Exit ticket

One question before you close the deck.

Predict first

Why does letter - 'a' give the position of a lowercase letter in the alphabet?

  • Because the lowercase letters occupy consecutive code points, so the difference is the offset from 'a'
  • Because Java has a special rule for subtracting characters
  • Because 'a' has code point 0
  • Because toLowerCase converts letters to their positions

Correct: Because the lowercase letters occupy consecutive code points, so the difference is the offset from 'a'

Why: A char is a number underneath — its Unicode code point — and the lowercase letters run consecutively from 97 for 'a' to 122 for 'z'. Subtracting 97 from any of them therefore gives 0 through 25. Note that this is a property of the encoding rather than a rule about alphabets: it is the same fact that let for (char c = 'a'; c <= 'z'; c++) walk the alphabet in Lesson 6a, and it is why uppercase input must be lowercased first — 'M' would give a negative index.

64. Draw the whole lesson

Connect it up

One page, from memory.

Draw it

Draw a counts array of ten boxes numbered 0 to 9, and trace the scores {3, 7, 3, 3, 7} into it one at a time, drawing an arrow from each score to the counter it increments. Then write the same loop twice — once with a standard for and once with an enhanced for — and mark which one could modify the scores array. Finally, write letter - 'a' and explain in one sentence why it gives 0 for 'a'.

65. Recap

Recap

Four sections that finish the chapter and answer the question it opened with: how to count 26 things without declaring 26 variables.

if you remember one thingit is this
about countinglet the value choose its own counter
about the enhanced forread with it, write without it
about charactersa char is a number, so you can do arithmetic on it

Sources

  1. Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 7 (Arrays and References), Sections 7.6-7.9, pp. 115-121
  2. Java SE 21 API — java.util.Random
  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