Where System.out actually comes from, how to read input from the keyboard with a Scanner, the vocabulary that makes compiler errors readable, naming magic numbers with final, and printf for output you control to the decimal place. Follows Think Java 2e, Chapter 3 (Input and Output), Sections 3.1-3.5, pp. 33-40, cross-referenced against Java SE 21 API — java.util.Scanner.
Subject: Java · 65 slides · code lesson
Open the interactive version of this deck
Title
Think Java 2e · Chapter 3 · Input and Output
Sections 3.1-3.5 · pp. 33-40
Objectives
This lesson follows Think Java 2e, Chapter 3 (Input and Output), Sections 3.1-3.5, pp. 33-40. Everything on these slides can be checked against those pages.
1. Explain what System, System.out and PrintStream are, and what a package is.
2. Import and create a Scanner, and read a line of input from the keyboard.
3. Name the elements of a Java program from package down to token, and use that vocabulary to read an error message.
4. Identify a magic number and replace it with a named constant declared final.
5. Use printf with a format string, and choose the right format specifier for an integer, a double or a string.
6. Say why the Java language and the Java library are different things.
Warm-up
You have typed one phrase hundreds of times without asking what it means.
Discussion prompt
System.out.println("hi"); has three names in it separated by dots: System, out, and println. Guess what each one is — which is a class, which is a variable, which is a method?
Hint: Class names start with a capital letter. Variables and methods start lowercase.
Answer:
System is a class, out is a variable inside it, and println is a method belonging to whatever out holds. The capitalisation told you: System is capitalised like every class, out and println are not.
That reading — class, then variable, then method — is exactly what Section 3.1 confirms, and it is the first time the dots have meant anything rather than being part of a magic phrase.
Concept
Every program so far has produced the same output every time it ran. This chapter adds input, so a program's behaviour depends on what a person types — and printf, so the output is formatted the way you want rather than the way Java defaults to.
Figure (svg): A pipeline showing keyboard input flowing into a Scanner, then a calculation, then printf, then formatted output
final constantsDowney & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 3 (Input and Output), Sections 3.1-3.5, pp. 33-40 — Chapter 3 opens on printed page 33.
Section
Section 3.1
Concept
System is a class that provides methods related to the system, or environment, where programs run. It also provides System.out, a special value with additional methods — like println — for displaying output. You can prove it is a value by printing it.
System.out.println(System.out);| part of the output | what it tells you |
|---|---|
| java.io.PrintStream | System.out is a PrintStream, defined in the java.io package |
| @ | separates the type from the address |
| 685d72cd | the address in memory, in hexadecimal — yours will differ |
package — A collection of related classes. java.io contains classes for I/O, which stands for input and output.
address — The location of a value in the computer's memory, often shown as a hexadecimal number.
The result is something like java.io.PrintStream@685d72cd. If you run the same code you will very likely get a different address — it is a location in memory, not a name.
Picture it
Three names, two dots, and each step goes one level inward. Once you can read this, every dotted expression in Java reads the same way.
Figure (svg): A banner breaking System.out.println into three parts labelled class, variable and method
System is defined in a file called System.java, and PrintStream in PrintStream.java. Both are part of the Java library — an extensive collection of classes you can use in your programs, whose source code usually ships with the compiler.
Worked example
Apply the reading to an expression you have not been taught yet, and see how far the rule gets you.
System.out.println(System.out);
// java.io.PrintStream@685d72cd| step | what you are looking at | conclusion |
|---|---|---|
| System | capitalised | a class |
| System.out | lowercase, inside a class | a variable belonging to that class |
| the printed type | java.io.PrintStream | the variable holds a PrintStream |
| System.out.println | lowercase, called with parentheses | a method belonging to the PrintStream |
Split the expression at the dots.
Why: System, then out, then println — three names, read left to right.
Use capitalisation to classify each one.
Why: A capital first letter means a class; lowercase means a variable or a method.
Use parentheses to tell a method from a variable.
Why: println(...) is called, so it is a method. out is not, so it is a variable.
Read the whole thing as a sentence.
Why: In the class System, take the variable out, and call its println method.
Verify: Run System.out.println(System.out); and check the output names a PrintStream in java.io.
Why: If the address you get differs from the book's, that is correct — the address is where the value happens to live in your machine's memory this run.
Definition probe
Use capitalisation and parentheses to decide.
Sort into buckets
Sort each name from the expressions you have seen.
Concept
A package is a directory of classes that are related to each other. The full name of a class includes its package, which is why the output above says java.io.PrintStream rather than just PrintStream.
| package | contains | imported automatically? |
|---|---|---|
| java.lang | classes fundamental to the language — System, String | yes |
| java.io | input and output — PrintStream | no |
| java.util | utility classes — Scanner | no |
The first row explains something you may have wondered about: why you can use System and String without importing anything. They are in java.lang, which Java imports for you. Everything else you have to ask for — which is the next section.
Trap
The assumption. System.out.println is one long name for one thing.
// if it were one name, this would be meaningless:
System.out.println(System.out);| if it were one name... | but in fact... |
|---|---|
System.out could not be printed on its own | it prints as java.io.PrintStream@... |
| there would be one method involved | there is a class, a variable, and a method |
| you could never use out for anything else | you can pass it around like any other value |
Treating it as one magic phrase works for a while, and then stops — the first time you see System.err.println or store a PrintStream in a variable.
Three separate things, joined by dots that each mean 'inside'.
System.out.println(System.out);
// System -> a class
// out -> a variable holding a PrintStream
// println -> a method of PrintStream| name | kind | how you can tell |
|---|---|---|
| System | class | capitalised |
| out | variable | lowercase, no parentheses |
| println | method | lowercase, called with parentheses |
The same reading works for in.nextLine() on the next slides: in is a variable, nextLine is a method of whatever it holds. One rule, applied everywhere.
Prediction
You have seen the answer; make sure you know why each part is there.
Predict first
Which part of java.io.PrintStream@685d72cd will differ if you run the program again?
Correct: 685d72cd — the address in memory
Why: The package and the type are fixed properties of what System.out is, so they never change. The part after the @ is the address — where that value happens to be located in this run's memory — and it may differ every time you run the program and on different computers.
Matching
Three classes you have met or are about to meet.
Match the pairs
Why: System and String live in java.lang, which Java imports for you because those classes are fundamental to the language. PrintStream is in java.io and Scanner in java.util — and the fact that Scanner needs an import while System does not is entirely explained by which package each one is in.
Socratic
A question about what an address actually is.
Discussion prompt
The number after the @ is described as the location of the value in the computer's memory. Why would that location not be the same every time the same program runs on the same computer?
Hint: What else is running on the machine?
Answer:
Because your program is not the only thing using memory. The operating system hands out whatever space is free when your program asks, and what is free depends on everything else running at that moment.
The useful consequence: an address is not an identity. It tells you where something is right now, not what it is. That distinction becomes important in Chapter 9, where two objects at different addresses can hold equal values — and comparing the addresses gives the wrong answer.
Section
Section 3.2
Concept
The System class also provides System.in, an InputStream with methods for reading from the keyboard. Those methods are not convenient, so Java provides Scanner, a class with methods for inputting words, numbers and other data.
import java.util.Scanner;
public class Echo {
public static void main(String[] args) {
String line;
Scanner in = new Scanner(System.in);
System.out.print("Type something: ");
line = in.nextLine();
System.out.println("You said: " + line);
}
}| line | what it does |
|---|---|
| import java.util.Scanner; | tells the compiler which Scanner you mean |
| Scanner in = new Scanner(System.in); | creates a Scanner that reads from the keyboard |
| System.out.print("Type something: "); | prompts, using print so the user types on the same line |
| line = in.nextLine(); | reads a line of input and returns it as a String |
| System.out.println("You said: " + line); | echoes it back, using concatenation from Chapter 2 |
nextLine reads a line of input from the keyboard and returns a String — which is why line is declared as a String rather than anything else.
Notation
Every program in the rest of this book that reads from the keyboard begins with these three lines. Learn them as a unit.
Annotate
Scanner, you mean the one defined in java.util — there might be another class named Scanner in another package.cannot find symbol, which means the compiler does not know where to find the definition for Scanner.new Scanner(System.in) creates a Scanner and tells it where to read from: System.in, the keyboard. The word new is doing real work here, and Chapter 10 explains it properly.in is just a variable name. You could call it keyboard or sc. in is conventional because it reads well next to System.in.Only the third line changes from program to program — and only to choose which next... method you call.
Worked example
The book's complete example. Trace what the user sees and what the program stores at each step.
Scanner in = new Scanner(System.in);
System.out.print("Type something: ");
String line = in.nextLine();
System.out.println("You said: " + line);
System.out.print("Type something else: ");
line = in.nextLine();
System.out.println("You also said: " + line);| step | on screen | value of line |
|---|---|---|
| print the prompt | Type something: | — |
| nextLine waits | user types: hello | — |
| nextLine returns | — | "hello" |
| println the echo | You said: hello | "hello" |
| second prompt and read | user types: again | "again" |
| println the echo | You also said: again | "again" |
Use print, not println, for the prompt.
Why: So the user types on the same line as the question, which is what people expect.
Call in.nextLine() and store what it returns.
Why: The program stops and waits here until the user presses Enter.
Reuse the same variable for the second read.
Why: line = in.nextLine(); is an assignment, not a declaration — the variable already exists and its old value is replaced.
Verify: Run it, type two different phrases, and confirm each is echoed back under the right label.
Why: If the program does not wait for you, check that you called nextLine() with parentheses — without them you have named the method rather than calling it.
Ranking
Four lines, one correct order.
Put in order
Why: The import comes first and lives outside the class entirely. Inside main you must create the Scanner before using it, prompt before reading so the user knows what to type, and only then read. Prompting after reading is a real and confusing bug — the program appears to hang.
Concept
You have been using System since Chapter 1 without importing anything, and now you have to import Scanner. The difference is which package each one lives in.
Figure (svg): A rule card stating that java.lang is imported automatically while every other package must be imported explicitly
According to the documentation, java.lang provides classes that are fundamental to the design of the Java programming language. The String class is part of it too — which is why you have been declaring String variables without importing anything.
Trap
The omission. The Scanner code is right; the file is missing one line at the top.
public class Echo {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String line = in.nextLine();
}
}| what is missing | the message | what it means |
|---|---|---|
| import java.util.Scanner; | cannot find symbol | the compiler does not know where Scanner is defined |
| — | symbol: class Scanner | it is telling you exactly which name it could not resolve |
cannot find symbol is one of the most common compile errors you will meet, and a missing import is one of its two usual causes. The other is a misspelled variable name.
With the import, above the class definition.
import java.util.Scanner;
public class Echo {
public static void main(String[] args) {
String line;
Scanner in = new Scanner(System.in);
System.out.print("Type something: ");
line = in.nextLine();
System.out.println("You said: " + line);
}
}| placement | legal? |
|---|---|
| above the class definition | yes — this is the convention |
| inside the class | no — import statements cannot be inside a class definition |
| inside main | no — same reason |
Note the second and third rows: this is not merely a style convention. An import inside a class is a compile error, so there is only one place it can go.
Prediction
The declaration on the left has to match.
Scanner in = new Scanner(System.in);
??? line = in.nextLine();| method | returns |
|---|---|
| nextLine() | a String — a whole line of text |
| nextInt() | an int |
| nextDouble() | a double |
Predict first
What type must line be declared as?
Correct: String
Why: nextLine reads a line of input and returns it as a String, so the variable receiving it must be a String. Even if the user types 42, nextLine hands back the two-character text "42" rather than the number — a distinction from Chapter 2 that matters a great deal here.
Fill the middle
Complete the three lines that begin every interactive program.
Fill in the blanks
import java.util.Scanner;
public class Ask new} Scanner(System.in);
String answer = in.nextLine();
}
}
Why: import tells the compiler where Scanner is defined, new creates one, and nextLine reads a whole line and returns it as a String. Omitting the import gives 'cannot find symbol'; omitting new gives a different error, because Scanner is a class rather than a value you can assign directly.
Missing information
Look at what is passed to it.
Discussion prompt
new Scanner(System.in) passes System.in as an argument. What information is that giving the Scanner, and what would you pass instead if you wanted to read from somewhere other than the keyboard?
Hint: The Scanner reads characters. From where?
Answer:
It tells the Scanner where to read from. System.in is the keyboard, so new Scanner(System.in) means 'a Scanner that reads what the user types'.
A Scanner does not have to read the keyboard. Pass it a File and it reads that file; pass it a String and it reads the characters of that string. The class is one piece of machinery — reading words, numbers and lines out of a stream of characters — and the argument decides which stream.
That is a pattern worth noticing early: the same class, pointed at a different source. You will meet it repeatedly, and it is why learning Scanner properly is worth more than learning 'how to read the keyboard'.
Section
Section 3.3
Concept
At this point you have seen nearly all of the organisational units that make up Java programs. They nest inside one another, largest to smallest — and knowing the names is what makes error messages readable.
Figure (svg): Six nested levels from package down to token, each labelled with an example
Comparing Java to English: statements are complete sentences, expressions are phrases, and tokens are individual words and punctuation marks.
Notation
The book's own example. Count the tokens, find the expression, and notice what is not counted.
Annotate
hours, =, minutes, /, 60.0 and ;. Spaces are ignored by the compiler, so they are not tokens.minutes / 60.0. An expression represents a single value to be computed — here, the result of the division.An unexpected token error means the compiler found a word or symbol somewhere it cannot appear. Illegal start of expression means it expected something that computes a value and got something else.
Worked example
A concrete payoff. Here is a broken line and the message it produces; use the hierarchy to locate the problem.
int x = ;| the message | which level it names | what to look for |
|---|---|---|
| illegal start of expression | expression | something that should produce a value is missing |
| not a statement | statement | a line that computes something but does not do anything with it |
| unexpected token | token | a symbol somewhere it cannot legally appear |
| cannot find symbol | token — a name | a misspelled name, or a missing import |
Read the message and identify which level it names.
Why: illegal start of expression names the expression level.
Look for the place an expression was expected.
Why: After = in an assignment, Java expects an expression. There is a semicolon instead.
Fix at that level.
Why: int x = 0; supplies the missing expression.
Note what the message did NOT say.
Why: It did not mention int or x, because the declaration part is fine. The message is narrower than it first appears.
Verify: Fix the line and recompile; the error disappears with no new one taking its place.
Why: The habit worth forming: read the message, name the level, then look only at that level. It turns a vague complaint into a specific search.
Ranking
The nesting the book draws.
Put in order
Why: Packages contain classes, classes define methods, methods are sequences of statements, statements contain expressions, and expressions are built from tokens. The English analogy holds throughout: statements are sentences, expressions are phrases, and tokens are words and punctuation.
Concept
There is a big difference between the Java language, which defines the elements above, and the Java library, which provides the built-in classes you can import.
| part of the language | part of the library |
|---|---|
| public, class, static, void, int | PrintStream, Scanner, String, System |
| the keywords, about 50 of them | several thousand classes |
| defined by the language specification | written in Java, mostly |
| cannot be changed or replaced | you can write your own classes alongside them |
The standard edition of Java comes with several thousand classes, which can be both exciting and intimidating. You can browse the whole library on Oracle's website — and interestingly, most of the Java library is itself written in Java.
Error analysis
Each of these names a different level of the hierarchy. Matching the message to the level is most of the diagnosis.
Annotate
cannot find symbol — a NAME the compiler cannot resolve. Either it is misspelled, or the class it names has not been imported. Here it is a missing import java.util.Scanner;.illegal start of expression — the compiler reached a point where a value should be computed and found something that cannot start one. After = it needs an expression, and a semicolon is not one.not a statement — x + 1; computes a value and then throws it away. That is a legal expression but not a legal statement, because a statement has to do something.unexpected token — a symbol appearing where the grammar does not allow it. The extra closing parenthesis has no partner, so the compiler cannot fit it into the structure.This slide is the reason Section 3.3 exists. It looks like a list of definitions and it is actually a decoder for the messages you will read hundreds of times.
Definition probe
One is defined by the specification; the other is a collection of classes someone wrote.
Sort into buckets
Sort each name.
Prediction
Remember what the compiler ignores.
int x = 5;| token | kind |
|---|---|
| int | keyword |
| x | variable name |
| = | operator |
| 5 | number |
| ; | punctuation |
Predict first
How many tokens are in int x = 5;?
Correct: 5
Why: The tokens are int, x, =, 5 and ; — five of them. Spaces are ignored by the compiler and are not tokens, which is the same fact that made publicclass illegal in Chapter 1: whitespace separates tokens but is not one.
Explain it to yourself
Make the case in your own words.
Discussion prompt
Section 3.3 is a list of definitions with no new code in it. Explain what it actually buys you, using one specific error message as your example.
Hint: Think about what you would do differently on seeing 'illegal start of expression'.
Answer:
It turns a vague complaint into a targeted search. Without the vocabulary, illegal start of expression means 'something is wrong'. With it, it means 'somewhere a value should be computed and is not' — so you look at the right-hand sides of assignments and the insides of parentheses, and nowhere else.
The general principle is worth more than the specific case: compiler messages are written in terms of the language's own structure. Learning that structure is what makes the messages readable, and it is why Think Java spends a section on vocabulary right after introducing a class you will misuse.
Section
Section 3.4
Concept
A value that appears in a program, like the number 2.54, is called a literal. There is nothing wrong with literals in general — but when a number appears in an expression with no explanation, it makes the code hard to read, and if the same value appears many times it makes the code hard to maintain.
int inch;
double cm;
Scanner in = new Scanner(System.in);
System.out.print("How many inches? ");
inch = in.nextInt();
cm = inch * 2.54; // where does 2.54 come from?| version | readable? | maintainable? |
|---|---|---|
| cm = inch * 2.54; | no — the reader must know the conversion | no — the value could appear in many places |
| double cmPerInch = 2.54; cm = inch * cmPerInch; | yes — the name explains it | better — one place to change |
| final double CM_PER_INCH = 2.54; | yes | yes — and it cannot be reassigned by accident |
literal — A value that appears directly in a program, such as 2.54 or "Hello".
magic number — A literal appearing in an expression with no explanation of where it came from.
The implication of the word magic is that being magic is not a good thing. A good practice is to assign such numbers to variables with meaningful names.
Picture it
Each fixes one more problem than the last.
Figure (svg): Two panels comparing a bare magic number against a named final constant
The middle step — a plain variable with a good name — fixes readability but not correctness. Variables can vary, and the number of centimetres in an inch does not.
Worked example
Three refinements, each fixing something specific. Work through them in order and notice what each one adds.
// 1. magic number
cm = inch * 2.54;
// 2. named, but still a variable
double cmPerInch = 2.54;
cm = inch * cmPerInch;
// 3. named and fixed
final double CM_PER_INCH = 2.54;
cm = inch * CM_PER_INCH;| version | explains itself? | can be reassigned? | naming style |
|---|---|---|---|
| 2.54 inline | no | n/a | — |
| double cmPerInch | yes | yes — a risk | lowerCamelCase, like any variable |
| final double CM_PER_INCH | yes | no — the compiler forbids it | ALL_CAPS with underscores |
Give the number a meaningful name.
Why: The reader no longer has to know that 2.54 is centimetres per inch — the code says so.
Notice the remaining problem.
Why: Variables can vary, hence the term. But the number of centimetres in an inch does not, and nothing so far prevents a later line from changing it.
Declare it final.
Why: Declaring that a variable is final means it cannot be reassigned once it has been initialised. If you try, the compiler gives an error.
Rename it in the constant style.
Why: By convention, names for constants are all uppercase with underscores between words: CM_PER_INCH.
Verify: Add the line CM_PER_INCH = 2.55; after the declaration and confirm the compiler refuses to build the program.
Why: That refusal is the whole point: final turns a rule you were relying on remembering into a rule the compiler enforces.
Definition probe
Not every literal is a magic number. The test is whether a reader would have to ask where it came from.
Sort into buckets
Sort each literal by whether it should be replaced with a named constant.
Concept
final is the first keyword you have met whose job is to forbid something. It is worth being clear about what it prevents and what it does not.
| with final | without | |
|---|---|---|
| can be read | yes | yes |
| can be initialised once | yes | yes |
| can be reassigned later | no — compile error | yes |
| names an unchanging fact | says so explicitly | leaves it to convention |
| naming convention | CM_PER_INCH | cmPerInch |
The third row is the enforced one; the fourth is the communicative one. Both matter — the compiler stops you making the mistake, and the ALL_CAPS name tells every future reader that this value is not going to move.
Trap
Correct but misleading. The value is fixed; the name says otherwise.
final double cmPerInch = 2.54;
final int inPerFoot = 12;| what the compiler sees | what a reader sees |
|---|---|
| a constant — reassignment is forbidden | an ordinary variable that might change |
| no error | no signal that this value is fixed |
This compiles and behaves correctly. The cost is entirely to the reader, which makes it exactly the kind of problem that survives into production code.
Constant style: all uppercase, underscores between words.
final double CM_PER_INCH = 2.54;
final int IN_PER_FOOT = 12;| kind of name | convention | example |
|---|---|---|
| class | UpperCamelCase | Scanner |
| variable | lowerCamelCase | cmPerInch |
| method | lowerCamelCase | nextLine |
| constant | ALL_CAPS_WITH_UNDERSCORES | CM_PER_INCH |
Four naming conventions, and you have now met all of them. None is enforced by the compiler; all are followed throughout the Java library, so a reader relies on them.
Prediction
A constant is reassigned.
final double CM_PER_INCH = 2.54;
CM_PER_INCH = 2.55;| line | legal? |
|---|---|
| final double CM_PER_INCH = 2.54; | yes — the one permitted initialisation |
| CM_PER_INCH = 2.55; | no — reassignment of a final variable |
Predict first
What happens when you compile this?
Correct: A compile error — a final variable cannot be reassigned
Why: Declaring a variable final means it cannot be reassigned once it has been initialised, and the compiler enforces that rather than merely recommending it. Catching this at compile time rather than at run time is the point: the mistake can never reach a user.
Matching
Four kinds of name, four styles.
Match the pairs
Why: Variables and methods share a style, distinguished in use by the parentheses that follow a method call. Classes are capitalised and constants shout. None of this is enforced by the compiler, but the entire Java library follows it, so a reader who sees ALL_CAPS correctly expects a value that will not change.
Constraint
Push on what final can and cannot express.
Discussion prompt
You want a constant for the number of students in a class, but the number is typed in by the user when the program starts. Can that be final? What exactly does final promise?
Hint: Read the promise carefully: it says 'cannot be reassigned once it has been initialised'.
Answer:
Yes, it can. final does not mean 'known when you write the program' — it means assigned exactly once. final int CLASS_SIZE = in.nextInt(); is perfectly legal, and any later attempt to change it is a compile error.
So final expresses this value will not change during the run, which is a weaker and more useful promise than this value is a known constant of the universe. The ALL_CAPS naming convention is conventionally reserved for the latter — for a value read at start-up, most programmers would write final int classSize and keep the ordinary style.
Section
Section 3.5
Concept
When you output a double with print or println, it displays up to 16 decimal places — which might be more than you want. System.out provides another method, printf, that gives you more control. The f stands for formatted.
System.out.print(4.0 / 3.0);
// 1.3333333333333333
System.out.printf("Four thirds = %.3f", 4.0 / 3.0);
// Four thirds = 1.333| part of the call | name | job |
|---|---|---|
| "Four thirds = %.3f" | the format string | specifies how the output should be displayed |
| Four thirds = | ordinary text | printed literally |
| %.3f | a format specifier | display the next value as floating-point, to 3 decimal places |
| 4.0 / 3.0 | the value | matched up with the specifier |
format string — The first argument to printf: ordinary text with format specifiers embedded in it.
format specifier — A special sequence starting with a percent sign that says how one value should be displayed.
A format specifier always starts with a percent sign. Everything else in the format string is printed exactly as written.
Notation
Learning about format strings is like learning a sublanguage within Java. There are many options; these seven cover nearly everything you will need.
Annotate
%d is for integers — d stands for decimal, meaning base 10, not 'decimal point'. This trips people up: %d will not accept a double.%f is for floating-point, and on its own it shows six decimal places. %.2f rounds to two, which is what you want for money.%s is for strings, and it is the most forgiving — nearly anything can be displayed as a string.%08d pads with zeros to at least eight digits, which is how you line up columns.For the full set, the documentation of java.util.Formatter is the reference. The easiest way to find documentation for any Java class is a web search for Java and the class name.
Worked example
The format string can contain any number of format specifiers. The values after it are matched up in order.
int inch = 100;
double cm = inch * CM_PER_INCH;
System.out.printf("%d in = %f cm\n", inch, cm);| specifier | gets which value | displayed as |
|---|---|---|
| %d | inch, which is 100 | 100 |
| %f | cm, which is 254.0 | 254.000000 |
| \n | — | ends the line |
Write the literal text and drop a specifier in wherever a value goes.
Why: "%d in = %f cm" — two values, so two specifiers.
List the values after the format string, separated by commas.
Why: inch, cm — in the same order as the specifiers that will consume them.
Match the specifier to the type.
Why: inch is an int so it takes %d; cm is a double so it takes %f.
Remember the newline.
Why: Like print, printf does not append a newline, so format strings often end with one.
Verify: Run it and expect 100 in = 254.000000 cm followed by a line break.
Why: If the output ran into whatever came next, you left the newline out of the format string — printf never adds one for you.
Six decimal places on 254.0 is more than anyone wants; %.2f would give 254.00, and %.0f would give 254.
Prediction
Two specifiers, two values, matched in order.
System.out.printf("%s is %d\n", "x", 5);| specifier | value | output |
|---|---|---|
| %s | "x" | x |
| %d | 5 | 5 |
Predict first
What appears on the screen?
Correct: x is 5
Why: The values are matched up with the specifiers in order, so "x" fills the %s and 5 fills the %d, while the literal words is and the space are printed as written. The quotation marks are not part of the string's value, so they do not appear.
Concept
Most of the time the choice is decided by the type of the value. The interesting decisions are about how many digits to show.
| you want to display | specifier | result |
|---|---|---|
| a whole number of items | %d | 42 |
| money | %.2f | 6.79 |
| a large count, readably | %,d | 12,345 |
| a name or any text | %s | Hello |
| a percentage, no decimals | %.0f | 98 |
| a fixed-width code | %08d | 00012345 |
The money row is the one to remember. %.2f rounds to two decimal places for display — which is not the same as storing money accurately, as Lesson 2b warned. Store cents in an int; format for display only.
Trap
The mismatch. The specifier says integer; the value is floating-point.
double cm = 254.0;
System.out.printf("%d cm\n", cm);| specifier | expects | given | result |
|---|---|---|---|
| %d | an integer | a double | IllegalFormatConversionException at run time |
This compiles cleanly — the compiler does not check the format string against the values — and then throws at run time. It is a run-time error, not a compile-time one, which makes it easy to ship.
Match the specifier to the type of the value.
double cm = 254.0;
System.out.printf("%.1f cm\n", cm); // 254.0 cm
int inch = 100;
System.out.printf("%d in\n", inch); // 100 in| value type | specifier | output |
|---|---|---|
| int | %d | 100 |
| double | %f or %.1f | 254.000000 or 254.0 |
| String | %s | Hello |
If you genuinely want a double shown with no decimal places, use %.0f rather than %d. The specifier describes how to display the value, and it cannot change what type the value is.
Matching
Four specifiers from the table.
Match the pairs
Why: %d prints an integer plainly and %,d adds comma separators to the same value. %.2f rounds a floating-point number to two decimal places, and %s displays text. The letter chooses the kind of value; anything between the percent sign and the letter adjusts the presentation.
Fill the middle
Produce the line: Total: 6.79
Fill in the blanks
double total = 6.789;
System.out.printf("Total: %.2f%n", total);
Why: %.2f displays a floating-point value rounded to two decimal places, giving 6.79 from 6.789. Using plain %f would show six decimal places (6.789000), and %d would throw at run time because the value is a double rather than an integer.
Edge cases
The percent sign now has a special job. Ask the escaping question from Lesson 1b.
Discussion prompt
In a format string, % starts a format specifier. So how would you print an actual percent sign — for example, the output 98% complete? Reason it out from what you know about escaping before looking it up.
Hint: How did you print a literal backslash once backslash became special?
Answer:
You double it: %%. System.out.printf("%d%% complete%n", 98); displays 98% complete.
This is exactly the pattern from Lesson 1b — a character was promoted to punctuation, so it needs a way to escape itself. The backslash escaped itself with \\; the percent sign escapes itself with %%. Different syntax, identical idea, and recognising the pattern means you can guess it rather than memorise it.
Note also %n in that example: it is printf's own newline specifier, and it is slightly more portable than \n because it uses whatever line ending the platform expects.
Comparison
Three ways to produce output, each better at something different. Fill the blanks.
Comparison matrix
| println | printf | ||
|---|---|---|---|
| appends a newline? | no | yes | no — end the format string with a newline |
| controls decimal places? | no | no | yes — %.2f and similar |
| how values are combined | concatenation with + | concatenation with + | specifiers, values listed after, separated by commas |
| best for | building a line in pieces | one complete line of plain output | formatted or aligned output |
The row that causes real bugs is the third: printf takes its values as separate arguments after a comma, not joined with +. The next lesson opens with what happens when you mix the two up.
Pattern
An interactive program has the same five-part shape every time, and you have now seen all five parts.
import java.util.Scanner; // 1. import
public class Convert {
public static void main(String[] args) {
final double CM_PER_INCH = 2.54; // 2. constants
Scanner in = new Scanner(System.in); // 3. create the Scanner
System.out.print("How many inches? "); // 4. prompt and read
int inch = in.nextInt();
double cm = inch * CM_PER_INCH; // 5. compute and format
System.out.printf("%d in = %.2f cm%n", inch, cm);
}
}| part | why it is there |
|---|---|
| import | names which Scanner you mean |
| final constants | gives every magic number a name that cannot be changed |
| new Scanner(System.in) | connects the program to the keyboard |
| print then nextInt | prompt on the same line, then read |
| printf | output formatted to the precision you chose |
final if they cannot change.Check
Work it out before you click.
Check your understanding
Why does a program using Scanner need an import statement while one using System does not?
Answer: A
Why: Java imports the java.lang package automatically because it provides classes fundamental to the language, and System and String both live there. Scanner is in java.util, so you must import it explicitly or the compiler reports 'cannot find symbol'.
new is unrelated to whether it needs importing — the two are decided by different things entirely.Check
Work it out before you click.
final int IN_PER_FOOT = 12;
int feet = 76 / IN_PER_FOOT;
IN_PER_FOOT = 10;| line | legal? |
|---|---|
| final int IN_PER_FOOT = 12; | yes |
| int feet = 76 / IN_PER_FOOT; | yes — reading a constant is fine |
| IN_PER_FOOT = 10; | no |
Check your understanding
What happens with this code?
Answer: A
Why: A variable declared final cannot be reassigned once it has been initialised, and the compiler enforces this, so the third line is a compile error and no program is produced. Reading the constant on the second line is perfectly fine — final restricts writing, not reading.
Check
Work it out before you click.
Check your understanding
Which printf call correctly displays the int 42 and the double 3.14159 rounded to two decimal places?
Answer: A
Why: The specifiers must match the types in order: %d takes the int 42 and %.2f takes the double 3.14159, displaying it as 3.14. The values follow the format string as separate arguments separated by commas.
Real world
The format-string idea is far older than Java and appears almost unchanged in a dozen languages.
Discussion prompt
You have just learned %d, %s and %.2f. Where else have you seen a string with placeholders in it that get filled in from a list of values?
Hint: Think about any templating you have seen, in any language or tool.
Answer:
The syntax came from C's printf in the 1970s and was carried into C++, Java, Python (% formatting), Ruby, PHP, Perl, Go and the shell's printf command — in most of them %d and %s mean exactly what they mean here.
The idea outlived the syntax too. Modern string interpolation — Python's f-strings, JavaScript's template literals, Java's own text blocks and formatters — is the same concept with the placeholders named rather than positional.
Why it matters to you now: the mistake in the next lesson — concatenating values onto a format string instead of passing them separately — is possible in every one of those languages, and it fails the same way in all of them.
Commit first
Commit to an answer and to your confidence.
Predict first
What does System.out.printf("%d", 3.0); do?
Correct: Compiles, then throws an exception at run time
Why: The compiler does not check a format string against the values that follow it — the format string is just a String as far as compilation is concerned. So this builds successfully and then throws IllegalFormatConversionException when the line runs, because %d requires an integer and it was handed a double. This is a good example of a mistake that the compiler cannot catch for you, which makes predicting your own output the only defence.
Explain it
Two minutes, out loud.
Discussion prompt
Explain to someone who has written a few programs why final double CM_PER_INCH = 2.54; is better than writing 2.54 where it is used. They will say it is more typing for no benefit. Give them two distinct reasons, one about reading and one about changing.
Hint: Imagine the number appearing in five places.
Answer:
Reading: inch * CM_PER_INCH says what the calculation means; inch * 2.54 requires the reader to already know the conversion. The name turns the code into its own explanation.
Changing: if the value appears in five places and needs correcting, you must find all five and change none of the other 2.54s that meant something different. With a constant there is exactly one place, and final guarantees nothing else has quietly overwritten it.
The 'more typing' objection is real and worth conceding — it is more typing. The trade is a few characters now against a class of bug later, and it is the same trade Java makes everywhere by asking you to declare types.
Exit ticket
One question before you close the deck.
Predict first
In Scanner in = new Scanner(System.in); what is System.in, and what is it telling the Scanner?
Correct: The input stream connected to the keyboard — it tells the Scanner where to read from
Why: System.in is a value provided by the System class — an InputStream connected to the keyboard — and passing it to the Scanner says which stream of characters to read. That is why the same Scanner class can also read a file or a string: only the argument changes. in on the left is the variable name, nextLine is the method that reads, and java.util is the package.
Connect it up
One page, from memory.
Draw it
Draw the six levels of a Java program from package down to token, as nested boxes, and put one example from this lesson in each. Beside it, write out the five-line skeleton of an interactive program — import, constant, Scanner, prompt-and-read, printf — and label which line each of the five would break if it were removed. Finally, write the three specifiers %d, %.2f and %s with the type each one requires.
Recap
Five sections that turn a program from something that talks at you into something you can talk to.
| if you remember one thing | it is this |
|---|---|
| about dots | class, then variable, then method |
| about numbers in code | give them names, and make them final |
| about printf | the specifier must match the value's type |
new Scanner(System.in), then call nextLine.final if it cannot change.Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.