The while (hasNextLine()) end-of-file loop
Student can write a while (sc.hasNextLine()) loop to read every line of a file, recognize the guard-and-read pairing, and adapt the pattern for counting lines (Pass 1 of count-allocate-fill) and for skipping a header before the loop.
The canonical file-reading shape: keep reading lines until the file has no more lines. Java's Scanner exposes this through a paired guard-and-read:
final Scanner sc = new Scanner(new File(filename));
while (sc.hasNextLine()) {
final String line = sc.nextLine();
// ... process line ...
}
sc.close();
The guard hasNextLine() looks ahead in the buffer without consuming any input. The body's nextLine() actually advances the cursor. When the file ends, hasNextLine() returns false, the loop exits, and the file is closed.
This loop has three properties that make it the right default for "I do not know how long the file is":
1. It is safe. A zero-line file enters the loop body zero times. A one-line file enters once. The guard never lies about whether content remains. 2. It is symmetric with the keyboard. The same shape works for new Scanner(System.in) when the input ends at EOF (Ctrl-D on Unix, Ctrl-Z on Windows). 3. It pairs cleanly with counting. When the goal is count the lines, the body is just count++.
Two common variations
Counting the lines (Pass 1 of count-allocate-fill):
int count = 0;
while (sc.hasNextLine()) {
sc.nextLine(); // discard the content; we only need the count
count++;
}
Skipping a header line before the loop:
sc.nextLine(); // throw away the header
while (sc.hasNextLine()) {
final String dataLine = sc.nextLine();
// ...
}
The header skip happens before the loop because the EOF loop treats every remaining line as a data line. Doing the skip inside the loop on the first iteration is possible but reads worse and fails when the loop runs zero times.
Why not a for loop?
for loops are right when you know the bound in advance: exactly N iterations. EOF loops are unbounded: the file determines how many iterations there are. Trying to write this as for (int i = 0; i < fileLength; i++) requires you to know fileLength ahead of time, which usually means a separate pass to count, defeating the purpose of EOF detection.
The exception: in Pass 2 of count-allocate-fill, where Pass 1 has already produced the count, the fill loop is a for loop. The guard-and-read shape is for "how big is this file?"; the counted for is for "now read exactly that many."
In other languages
- Python:
for line in f:: the loop terminates at EOF automatically. - C:
while (fgets(buf, sizeof buf, fp) != NULL) { ... }: sentinel-return at EOF. - Bash:
while read -r line; do ... done < file.txt: same shape.
The while (hasNext()) token-mode EOF loop
Student can produce a token-mode EOF loop using while (sc.hasNext()) { sc.next(); } (or the typed-guard variants hasNextInt/nextInt, etc.), distinguish the four common loop shapes by the question they answer (line-EOF, token-EOF, typed-EOF, counted), and identify the case where typed guards terminate on a sentinel token without an explicit if.
The token-mode analog of the hasNextLine loop: read tokens one at a time until the file's token supply is exhausted. The shape is identical; only the methods change.
final Scanner sc = new Scanner(new File(filename));
while (sc.hasNext()) {
final String token = sc.next();
// ... process the token ...
}
sc.close();
The guard hasNext() looks ahead in the buffer, skipping whitespace, and reports whether there is any non-whitespace token remaining. The body's next() consumes one. The loop terminates when hasNext() returns false: meaning the rest of the file is empty or all-whitespace.
The typed-EOF variants
When the tokens are all of one type, the typed guards work the same way:
// Sum all integers in a file
int sum = 0;
while (sc.hasNextInt()) {
sum += sc.nextInt();
}
hasNextInt() returns true only when the next token both (a) exists and (b) parses as an int. The loop stops on the first non-integer (or at EOF). This is the safest token loop because the guard ensures nextInt will not throw InputMismatchException.
The typed-guard pattern is also the idiom for "read integers until a sentinel":
// File ends with "STOP" instead of EOF
while (sc.hasNextInt()) {
int v = sc.nextInt();
// ... process v ...
}
// The cursor now sits before "STOP" (the first non-integer token)
// sc.next() would return "STOP" if a caller needed to consume it.
The pattern handles both EOF and sentinel termination without an if inside the loop: hasNextInt() is false in both cases.
Comparing the four common shapes
| Loop | When to use | |------|-------------| | while (sc.hasNextLine()) { sc.nextLine() } | Lines, one record per line | | while (sc.hasNext()) { sc.next() } | Free-form whitespace-delimited tokens, type unknown | | while (sc.hasNextInt()) { sc.nextInt() } | Integer-only stream; terminates at first non-int | | for (int i = 0; i < n; i++) { sc.next() } | Exactly n reads: count already known |
The first three are EOF loops (variable number of iterations); the fourth is a counted loop (fixed number). All four read from a Scanner; the differences are about what stops the reading.
Performance
The guard methods do not skip past tokens: they only peek. hasNext() is O(token-skip distance) in the worst case (when there is a lot of whitespace between tokens), and effectively O(1) for typical files. Calling the guard once per iteration adds no asymptotic cost; the loop body's next() is what advances the cursor.
In other languages
- Python: the for-line iterator handles the line case; token-mode is
for word in line.split()per line. - C:
while (fscanf("%d", &n) == 1) { ... }:fscanfreturns the count of successfully parsed items, including0(parse failed) andEOF(-1). The loop tests for "parsed something." - C++:
while (cin >> n) { ... }: stream conversion toboolreturnsfalseat EOF or on parse error. Same shape; different mechanism.
NoSuchElementException when reading past end-of-file
Student can predict that calling nextLine(), next(), or any nextX() method on a Scanner with no remaining input throws NoSuchElementException, identify the four common code-shape causes (no guard, extra read, wrong-paired guard, count-off-by-one), and read a stack trace to locate the offending line.
When any of Scanner's next/nextLine methods is called and there is nothing left to read, the call throws java.util.NoSuchElementException. This is the symptom of "I read past the end of the file." The exception is unchecked* (extends RuntimeException), so the compiler does not require try/catch: the program simply crashes with a stack trace.
final Scanner sc = new Scanner(new File("one-line.txt")); // file has exactly 1 line
String a = sc.nextLine(); // reads the line
String b = sc.nextLine(); // NoSuchElementException: No line found
The Javadoc message gives the relevant clue: "No line found" for nextLine(), "No more tokens" (or InputMismatchException for typed reads) for next() / nextInt(). The thrown class is the same NoSuchElementException (with InputMismatchException as a subclass for the parse-failure case).
The shape of the bug
The exception is the consequence, not the cause. The cause is always one of:
1. Loop with no guard. A for (int i = 0; i < n; i++) sc.nextLine(); where n is wrong (too large). The fix: use the EOF loop (while (sc.hasNextLine())) so the loop self-bounds. 2. One read too many after the loop. A loop that correctly consumes every token, followed by a stray sc.next(). The fix: delete the extra read. 3. Wrong guard paired with wrong read. while (sc.hasNext()) paired with sc.nextInt(): hasNext() is true if any token is available (including non-numeric ones), so the first non-integer token reaches nextInt() and throws. The pairing rule is hasNextX with nextX: match the typed guard to the typed read. (The token-mode end-of-file loop covers this in detail.) 4. Off-by-one in a counted-fill loop. Pass 2 of count-allocate-fill, which the course covers later, reads exactly n tokens. If Pass 1's count was wrong, Pass 2 either reads too few (no exception, but leaves data unread) or too many (NoSuchElementException).
Each cause has a different fix; the exception alone does not tell you which.
Diagnosis via the stack trace
The stack trace gives you the line number. Read it backward from the NoSuchElementException line:
Exception in thread "main" java.util.NoSuchElementException: No line found
at java.base/java.util.Scanner.nextLine(Scanner.java:1651)
at MyProgram.readData(MyProgram.java:42) // ← THIS line is the broken read
at MyProgram.main(MyProgram.java:15) // ← caller
Line 42 is where the broken read happened. Look around line 42: was it inside a loop? Was the loop guarded? If the guard is correct, the bug is elsewhere (the file is shorter than expected; line 15's caller passed the wrong file; an earlier read consumed too much).
Defensive reads
For input where the file's format is known (every record has exactly N fields), guarding every read is overkill. The CSCD 210 lab convention guards the outer loop (with hasNextLine or hasNext) and trusts each iteration's body to consume exactly the per-record fields:
while (sc.hasNext()) { // outer guard: any data left?
String name = sc.next();
int age = sc.nextInt(); // trusted: every record has both fields
// ... process ...
}
If a record is malformed (only the name, no age), the second read throws, but this is a malformed-input error, not a guard placement error. The program should fail loudly on malformed input.
In other languages
- Python: reading past EOF on a file iterator silently terminates the for-loop. Calling
f.readline()past EOF returns an empty string"", not an exception. - C:
fgetsreturnsNULLpast EOF;fscanfreturnsEOF. No exception mechanism. - C++:
cin >> nsetscin.eof()to true and the next conversion fails silently (leavingnzero on modern C++; undefined on older).