The three logical operators and the short-circuit evaluation that makes them safe, De Morgan's laws for negating a condition readably, boolean variables and methods, and the input validation that turns five lines of code into a program you could give to a stranger. Follows Think Java 2e, Chapter 5 (Conditionals and Logic), Sections 5.5-5.10, pp. 77-84, cross-referenced against The Java Tutorials — Equality, Relational, and Conditional Operators.
Subject: Java · 65 slides · code lesson
Open the interactive version of this deck
Title
Think Java 2e · Chapter 5 · Conditionals and Logic
Sections 5.5-5.10 · pp. 77-84
Objectives
This lesson follows Think Java 2e, Chapter 5 (Conditionals and Logic), Sections 5.5-5.10, pp. 77-84. Everything on these slides can be checked against those pages.
1. Combine conditions with &&, || and !, and predict when the second operand is not evaluated.
2. Apply De Morgan's laws to rewrite a negated condition without the exclamation mark.
3. Declare and use a boolean variable as a flag, and say why you never compare one to true.
4. Write a method that returns a boolean, and use it directly as a condition.
5. Validate input for both format and range, and say which run-time error each check prevents.
6. Use return to leave a method early, and System.err for error messages.
Warm-up
One structure from the previous lesson, which this one is about collapsing.
Discussion prompt
Write a nested conditional that prints a message only when both x and y are zero. Then look at how many lines and how many indentation levels it takes.
Hint: One if inside another.
Answer:
if (x == 0) { if (y == 0) { System.out.println("Both x and y are zero"); } } — two conditions, two levels of indentation, and the message buried three levels deep.
The && operator collapses that into a single condition: if (x == 0 && y == 0). Section 5.5 opens with exactly this rewrite, and it is the most immediately useful thing in the lesson.
Concept
The previous lesson could test one thing at a time. This one lets you combine conditions, negate them readably, store them, name them, and finally use them to make a program safe against whatever a user types.
Figure (svg): Three boxes naming the logical operators and what each requires to be true
Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 5 (Conditionals and Logic), Sections 5.5-5.10, pp. 77-84 — Sections 5.5-5.10, printed pages 77-84.
Section
Section 5.5
Concept
In addition to the relational operators, Java has three logical operators: &&, || and !, which stand for and, or and not. Their results are similar to their meanings in English.
x > 0 && x < 10 // true when x is greater than 0 AND less than 10
x < 0 || x > 10 // true if EITHER condition is true
!(x > 0) // true if x is NOT greater than 0| expression | true when | false when |
|---|---|---|
| A && B | both A and B are true | either one is false |
| A || B | either A or B is true | both are false |
| !A | A is false | A is true |
logical operator — An operator that combines boolean values and produces a boolean value.
The parentheses in !(x > 0) are necessary, because in the order of operations ! comes before >. Without them Java would try to negate x itself, which is not a boolean.
Notation
The two most useful things && and || do are remove a level of nesting and remove a duplicated branch.
Annotate
&& replaces nesting. Two conditions that both have to hold become one condition, and the body comes back up two indentation levels.|| replaces a chain with duplicated bodies. Since the branches were the same, there is no need to duplicate that code.Reading a condition aloud is a good test. if (x == 0 && y == 0) reads as if x is zero and y is zero, which is what you meant. The nested version reads as a set of instructions instead.
Worked example
Logical operators evaluate the second expression only when necessary. That is not merely an optimisation — it changes what code is safe to write.
// true || anything is always true
// false && anything is always false
if (count != 0 && total / count > 10) {
System.out.println("high average");
}| count | first operand | is the second evaluated? | result |
|---|---|---|---|
| 0 | 0 != 0 is false | no — short circuit | false, and no division happens |
| 4 | 4 != 0 is true | yes | depends on total / 4 |
Evaluate the left operand first.
Why: Java always works left to right.
Ask whether the answer is already decided.
Why: For &&, a false on the left settles it: false and anything is false.
If so, skip the right operand entirely.
Why: This is short-circuit evaluation, named by analogy with an electrical circuit.
Notice what that makes possible.
Why: The division by count never runs when count is zero, so the guard on the left prevents an ArithmeticException on the right.
Verify: Run it with count = 0 and confirm there is no divide-by-zero exception.
Why: Then swap the operands — total / count > 10 && count != 0 — and confirm it now crashes. The order of the operands is doing real work, which is the whole point of this slide.
Short-circuit evaluation can save time if the second operand is expensive to compute, and it can avoid unnecessary errors if the second operand might fail.
Prediction
Short-circuit evaluation, with a division that would fail.
int count = 0;
int total = 50;
if (count != 0 && total / count > 10) {
System.out.println("high");
}| operand | value | evaluated? |
|---|---|---|
| count != 0 | false | yes |
| total / count > 10 | would throw | no — short circuit |
Predict first
What happens when this runs?
Correct: Nothing is printed, and no exception occurs
Why: The left operand of && is false, and false and anything is false, so Java never evaluates the right operand — and the division by zero never happens. Short-circuit evaluation is what makes this guard pattern work, and reversing the two operands would make the program crash.
Concept
! binds tightest, then &&, then || — and all of them bind more loosely than the relational operators. That ordering is why most conditions need no parentheses at all.
| written | grouped as | note |
|---|---|---|
| x > 0 && x < 10 | (x > 0) && (x < 10) | relational before logical, so no parentheses needed |
| !x > 0 | (!x) > 0 | an error — ! binds tighter than > |
| !(x > 0) | not (x greater than 0) | the parentheses are required |
| a || b && c | a || (b && c) | && binds tighter than || |
The second row is the one that bites. Because ! comes before > in the order of operations, !x > 0 tries to negate x — and since x is not a boolean, it does not compile. When negating a comparison, the parentheses are not optional.
Trap
The mathematical form, which is not Java.
if (0 < x < 10) { // does not compile
System.out.println("in range");
}| step | what Java does | result |
|---|---|---|
| 0 < x | evaluates to a boolean | true or false |
| (that boolean) < 10 | compare a boolean with an int | compile error: bad operand types |
In mathematics this chained form is standard notation. In Java each relational operator takes two operands and produces a boolean, and a boolean cannot be compared with a number.
Two separate comparisons, joined with &&.
if (0 < x && x < 10) {
System.out.println("in range");
}| part | value for x = 5 |
|---|---|
| 0 < x | true |
| x < 10 | true |
| true && true | true |
Note that x has to be written twice. Every relational operator needs both of its operands spelled out, which is exactly why the mathematical shorthand cannot work.
Definition probe
Read each requirement in English and choose the operator.
Sort into buckets
Sort each requirement.
Fill the middle
True when x is at least 1 and at most 100.
Fill in the blanks
if (x >= 1 && x <= 100) ___
Why: Both conditions must hold, so they are joined with &&, and 'at most 100' includes 100 itself, so the operator is <=. Note that x must appear on both sides of the && — the mathematical shorthand 1 <= x <= 100 does not compile, because the first comparison produces a boolean that cannot then be compared with 100.
Edge cases
Push on short-circuit evaluation until it changes behaviour rather than just speed.
Discussion prompt
a && b and b && a are both true in exactly the same cases. Give an example where swapping them nonetheless changes what the program does — and say what that tells you about &&.
Hint: What if evaluating one of them could fail?
Answer:
count != 0 && total / count > 10 is safe; total / count > 10 && count != 0 throws when count is zero. The two expressions are logically equivalent and operationally different.
So && is not quite the and of mathematics: it is ordered. The left operand is a guard for the right, and that is a deliberate and very widely used feature — the same pattern appears in checking that something is not null before using it, which you will need in Chapter 9.
The habit to take: put the cheap or protective test on the left. It costs nothing when both are safe and prevents a crash when one is not.
Section
Section 5.6
Concept
Sometimes you need to negate an expression containing a mix of relational and logical operators. Doing it with ! and parentheses produces a condition that is hard to read. De Morgan's laws give a better way.
Figure (svg): A rule card stating both of De Morgan's laws for negating and and or expressions
// hard to read:
if (!(x == 0 || y == 0)) {
System.out.println("Neither x nor y is zero");
}
// after applying De Morgan:
if (x != 0 && y != 0) {
System.out.println("Neither x nor y is zero");
}| step | expression |
|---|---|
| start | !(x == 0 || y == 0) |
| negate each term | !(x == 0) ... !(y == 0) |
| change the operator | || becomes && |
| simplify the negations | x != 0 && y != 0 |
In words: negating a logical expression is the same as negating each term and changing the operator. The logic is identical and the source code is much easier to read.
Notation
De Morgan's laws also apply to the relational operators. Negating a term there means using the opposite operator, which is not always the one people reach for.
Annotate
< is >=, not >. Not-less-than includes equal, which is the single most common slip when negating a comparison.! takes precedence over && and ||, so you do not have to put parentheses around the individual terms !A and !B after applying the law.&& inside a negation becomes an ||, and vice versa. Forgetting that step produces a condition that is wrong for exactly the inputs you were trying to exclude.The practical value is readability. x != 0 && y != 0 can be read aloud and checked; !(x == 0 || y == 0) has to be worked out.
Worked example
Negate a two-part condition mechanically rather than by intuition, then check the result against a truth table.
// negate: age >= 18 && hasTicket
// step 1: negate each term
// step 2: change && to ||
// result: age < 18 || !hasTicket| age | hasTicket | original | negation |
|---|---|---|---|
| 20 | true | true | false |
| 20 | false | false | true |
| 15 | true | false | true |
| 15 | false | false | true |
Write down the original condition.
Why: age >= 18 && hasTicket — admitted only if both hold.
Negate each term separately.
Why: age >= 18 becomes age < 18; hasTicket becomes !hasTicket.
Change the operator.
Why: && becomes ||. This is the step people forget.
Check every row of the truth table.
Why: The negation must be true in exactly the rows where the original is false — all three of them.
Verify: Compare the last two columns: they are opposites in every row, which is what a correct negation means.
Why: If you had kept the && — age < 18 && !hasTicket — the second and third rows would both be false, and the condition would wrongly admit an adult with no ticket. Checking the table is what catches that.
Prediction
Apply the law mechanically.
// negate this condition:
x > 10 && y < 5| step | result |
|---|---|
| negate x > 10 | x <= 10 |
| negate y < 5 | y >= 5 |
| change && to || | x <= 10 || y >= 5 |
Predict first
Which expression is equivalent to !(x > 10 && y < 5)?
Correct: x <= 10 || y >= 5
Why: Each term is negated — the opposite of > is <= and the opposite of < is >=, both including equality — and the && becomes ||. The two common errors are visible in the other options: dropping the equality from the negated operators, and forgetting to flip the connective.
Concept
!(A && B) is perfectly correct Java. The argument for rewriting it is entirely about the person reading it next.
| written with ! | after De Morgan | which reads better aloud |
|---|---|---|
| !(x == 0 || y == 0) | x != 0 && y != 0 | the second — 'x is not zero and y is not zero' |
| !(a && b) | !a || !b | usually the second, if a and b have good names |
| !(score >= 50) | score < 50 | the second — 'the score is below 50' |
| !found | !found | the first — a single negated flag is already clear |
The last row matters: De Morgan is for compound conditions. A single negated boolean, especially one with a well-chosen name like found or valid, is already as readable as it is going to get.
Trap
Negating each term and leaving the && alone.
// intended: NOT (in stock AND affordable)
if (!inStock && !affordable) { // wrong
System.out.println("cannot buy");
}| inStock | affordable | should be true? | this condition |
|---|---|---|---|
| true | true | false | false — correct |
| true | false | true | false — WRONG |
| false | true | true | false — WRONG |
| false | false | true | true — correct |
It is correct in two rows out of four, which is why casual testing misses it. The failing rows are exactly the interesting ones — in stock but unaffordable, or affordable but out of stock.
Negate each term AND change the operator.
// NOT (inStock && affordable) -> !inStock || !affordable
if (!inStock || !affordable) {
System.out.println("cannot buy");
}| inStock | affordable | should be true? | this condition |
|---|---|---|---|
| true | true | false | false |
| true | false | true | true |
| false | true | true | true |
| false | false | true | true |
All four rows now agree. The four-row check takes under a minute and is the only reliable way to confirm a negation — intuition about compound conditions is unreliable even for experienced programmers.
Matching
Four negations, done correctly.
Match the pairs
Why: Each term flips to its opposite operator — and the opposite of < is >=, including equality — while && and || swap places. The last pairing is the book's own example, and it is the clearest demonstration of why the rewrite is worth doing: 'x is not zero and y is not zero' reads directly.
Elimination
Three of these apply the law correctly.
Eliminate the wrong options
Rule out the three correct ones and keep the mistake.
Survives elimination: C
Why: Option C negates both terms but leaves the && in place; the correct result is !a || !b. This is the most common De Morgan error, and it produces a condition that agrees with the intended one in two of four cases — which is exactly enough to survive casual testing.
Explain it to yourself
Without symbols.
Discussion prompt
State both of De Morgan's laws in plain English, without using any operator symbols. Then use your statement to negate: the file exists and is readable.
Hint: One sentence covers both laws.
Answer:
Not (A and B) means not-A or not-B; not (A or B) means not-A and not-B. Or in one sentence: to negate a compound condition, negate each part and swap 'and' with 'or'.
So the negation of the file exists and is readable is the file does not exist, or it is not readable. Notice how naturally that reads — and notice that the file does not exist and is not readable would be wrong, because it excludes the case of a file that exists but cannot be read.
Section
Section 5.7
Concept
To store a true or false value you need a boolean variable. You declare and assign them like any other variable — and since relational and logical operators evaluate to a boolean, you can store the result of a comparison.
boolean flag;
flag = true;
boolean testResult = false;
boolean evenFlag = (x % 2 == 0); // true if x is even
boolean positiveFlag = (x > 0); // true if x is positive| line | kind of statement |
|---|---|
| boolean flag; | a declaration |
| flag = true; | an assignment |
| boolean testResult = false; | both, in one statement |
| boolean evenFlag = (x % 2 == 0); | both — the value comes from a comparison |
flag — A variable, usually boolean, that represents a condition or status.
A variable defined this way is called a flag, because it signals — flags — the presence or absence of a condition. The parentheses around the comparison are unnecessary, but they make the code easier to understand.
Picture it
The value stored is exactly what the condition evaluated to. Naming it means the test can be done in one place and used in several.
Figure (svg): A trace strip showing a comparison producing true, being stored in a named flag, and then used as a condition
Flags may not seem that useful yet. They will help simplify complex conditions later: each part of a condition can be stored in a separate flag, and those flags combined with logical operators.
Worked example
Since a boolean variable already is a condition, testing it against true adds words and takes meaning away.
boolean evenFlag = (x % 2 == 0);
if (evenFlag) { // good
System.out.println("n was even when I checked it");
}
if (!evenFlag) { // good — to check for false
System.out.println("n was odd when I checked it");
}| written | correct? | reads as |
|---|---|---|
| if (evenFlag) | yes | if even flag |
| if (evenFlag == true) | works, but verbose | if even flag is equal to true |
| if (!evenFlag) | yes | if not even flag |
| if (evenFlag == false) | works, but verbose | if even flag is equal to false |
Notice that a boolean variable is already a boolean expression.
Why: if needs a boolean, and evenFlag is one — nothing further is required.
To test for false, negate the flag.
Why: !evenFlag rather than a comparison.
Read each version aloud.
Why: If even flag is a sentence; if even flag is equal to true is a sentence about a sentence.
Adopt the rule.
Why: In general you should never compare anything to true or false. It makes the code more verbose and awkward to read out loud.
Verify: Rewrite any == true you find in your own code and check the behaviour is unchanged.
Why: It always is — which is the point. The comparison never added anything, and removing it makes the intent clearer.
Prediction
A flag holding the result of a comparison.
int x = 7;
boolean isEven = (x % 2 == 0);
if (!isEven) {
System.out.println("odd");
} else {
System.out.println("even");
}| step | value |
|---|---|
| x % 2 | 1 |
| 1 == 0 | false |
| isEven | false |
| !isEven | true |
Predict first
What is displayed?
Correct: odd
Why: 7 is odd, so x % 2 is 1, the comparison with 0 is false, and isEven holds false. Negating it makes the condition true, so the first branch runs. Note that no comparison to true or false was needed anywhere — the flag is already a condition and ! is how you ask for the opposite.
Concept
A flag's whole value is its name. A well-named one makes a condition read as a claim about the program's state.
| poor name | better name | why |
|---|---|---|
| flag | isEven | says what the condition is |
| b | hasTicket | reads as a claim in a condition |
| check | inputIsValid | a noun phrase that can be true or false |
| notFound | found | avoids a double negative when you write !notFound |
The last row is worth dwelling on. A flag whose name contains a negative reads terribly when negated — if (!notFound) is a puzzle. Name the flag for the positive case and let ! do the work.
Trap
Verbose, and one typo away from a bug.
if (evenFlag == true) {
System.out.println("even");
}
if (evenFlag = true) { // a single = : compiles in some languages
System.out.println("even");
}| written | in Java | in C |
|---|---|---|
| evenFlag == true | works, verbose | works, verbose |
| evenFlag = true | compile error — an assignment is not a condition... | compiles, always true, sets the flag |
Java catches the single-= version because an assignment to a boolean produces a boolean — actually this one does compile in Java, and always evaluates to true while overwriting your flag. That is the one case where Java's type system does not save you.
Use the flag directly. The mistake becomes impossible to write.
if (evenFlag) {
System.out.println("even");
}
if (!evenFlag) {
System.out.println("odd");
}| goal | write |
|---|---|
| act when the flag is true | if (flag) |
| act when the flag is false | if (!flag) |
| never | if (flag == true) or if (flag = true) |
This is a genuine case where the shorter form is also the safer one. There is no = to mistype, because there is no comparison at all.
Definition probe
A flag's name should read as a claim that can be true or false.
Sort into buckets
Sort each flag name.
if (isEmpty) is a sentence — and negating it with ! still reads naturally.!notValid becomes a double negative the reader has to unpick.Fill the middle
Set a flag from a comparison, then act on it.
Fill in the blanks
boolean isAdult = (age >= 18);
if (!isAdult) ___
Why: The variable holds true or false so its type is boolean, and to act on the opposite case you negate the flag with !. Writing isAdult == false would work and is exactly the comparison to avoid — the flag is already a condition.
Socratic
You could always inline the comparison instead.
Discussion prompt
if (x % 2 == 0) works without a flag. When does storing the condition in a named boolean actually earn its line, and when is it just noise?
Hint: Think about using the same test more than once, and about complicated conditions.
Answer:
It earns its place when the condition is used more than once — you compute it once and cannot get the two copies out of step — or when it is complicated enough that the name explains it better than the expression does.
It is noise when the condition is short, obvious, and used once. boolean isPositive = x > 0; if (isPositive) adds a line and says nothing if (x > 0) did not.
The third case is the one Think Java is pointing at: breaking a complex condition into named parts. if (hasTicket && !isBanned && seatsRemaining) reads far better than the same three tests written out in full, and each flag can be checked separately while debugging.
Section
Section 5.8
Concept
Methods can return boolean values, just like any other type, which is often convenient for hiding tests inside methods. It is common to give such methods names that sound like yes/no questions.
public static boolean isSingleDigit(int x) {
if (x > -10 && x < 10) {
return true;
} else {
return false;
}
}| x | the condition | returns |
|---|---|---|
| 2 | 2 > -10 && 2 < 10 | true |
| 17 | 17 > -10 && 17 < 10 | false |
| -5 | -5 > -10 && -5 < 10 | true |
| -42 | -42 > -10 is false | false |
Since the return type is boolean, the return statement has to provide a boolean expression. The name isSingleDigit sounds like a question, which is what makes the call sites read well.
Notation
The code above is straightforward, and longer than it needs to be. The expression in the condition is already a boolean.
Annotate
x > -10 && x < 10 has type boolean, so there is nothing wrong with returning it directly, without the if statement at all.if (condition) return true; else return false; always simplifies to return condition;.true in one branch and false in the other, the condition itself is the answer.This is the same observation as never compare a boolean to true, seen from the other side: a boolean expression is already the value you want, so wrapping it in machinery adds nothing.
Worked example
In main you can invoke the method in the usual ways — and conditional statements often invoke boolean methods and use the result directly as the condition.
System.out.println(isSingleDigit(2)); // true
boolean bigFlag = !isSingleDigit(17); // true
if (isSingleDigit(z)) {
System.out.println("z is small");
} else {
System.out.println("z is big");
}| usage | what happens | result |
|---|---|---|
| println(isSingleDigit(2)) | the call returns true, println displays it | true |
| !isSingleDigit(17) | returns false, then negated | bigFlag is true |
| if (isSingleDigit(z)) | the returned boolean IS the condition | branches on the answer |
Print the result directly.
Why: println can display a boolean, so the first line shows true because 2 is a single-digit number.
Store a negated result in a flag.
Why: bigFlag becomes true, because 17 is not a single-digit number.
Use the call as a condition with no comparison.
Why: if (isSingleDigit(z)) — never if (isSingleDigit(z) == true).
Read the whole thing aloud.
Why: If is single digit z, print z is small, else print z is big. Examples like this one almost read like English.
Verify: Try z = 5 and z = 500 and confirm you get 'z is small' and 'z is big'.
Why: The readability is the point. A well-named boolean method turns a condition from a calculation into a sentence, and that is what makes complex logic manageable.
Fill the middle
Collapse the if-else into a single return.
Fill in the blanks
public static boolean isTeenager(int age) return} age >= 13 && age <= 19;
}
Why: The expression age >= 13 && age <= 19 already has type boolean, so it can be returned directly with no if statement at all. Both bounds are inclusive, which is why both operators include equality — a 13-year-old and a 19-year-old are both teenagers.
Concept
Boolean methods are not a new idea in the library. You are about to use one that has existed all along.
| method | belongs to | asks |
|---|---|---|
| in.hasNextDouble() | Scanner | can the next input be read as a double? |
| in.hasNextInt() | Scanner | can it be read as an int? |
| in.hasNextLine() | Scanner | is there another line? |
| isSingleDigit(x) | yours | is x a single-digit number? |
All four names are yes/no questions, and all four are used the same way — directly as a condition. The next section uses the first of them to make a program safe against bad input.
Trap
The long form, and the bug it makes possible.
public static boolean isPositive(int x) {
if (x > 0) {
return false; // swapped by mistake
} else {
return true;
}
}| x | should return | actually returns |
|---|---|---|
| 5 | true | false |
| -5 | false | true |
| 0 | false | true |
Wrong for every input, and the method still compiles and looks plausible. The two returns are easy to swap and there is nothing to check them against.
Return the condition itself. There is nothing left to get backwards.
public static boolean isPositive(int x) {
return x > 0;
}| x | x > 0 | returns |
|---|---|---|
| 5 | true | true |
| -5 | false | false |
| 0 | false | false |
One line, one place to be wrong, and the condition reads exactly as the method's name promises. Whenever you write return true in one branch and return false in the other, collapse it.
Prediction
A boolean method used three ways.
public static boolean isSingleDigit(int x) {
return x > -10 && x < 10;
}
// in main:
System.out.println(isSingleDigit(2));
System.out.println(isSingleDigit(17));| call | condition | returns |
|---|---|---|
| isSingleDigit(2) | 2 > -10 && 2 < 10 | true |
| isSingleDigit(17) | 17 < 10 is false | false |
Predict first
What are the two lines of output?
Correct: true then false
Why: 2 lies between -10 and 10 so both parts of the condition hold and the method returns true; 17 fails the second part so the whole && is false. println can display a boolean directly, printing the words true and false.
Discrimination
A boolean method's name should sound like a yes/no question.
Sort into buckets
Sort each method name.
is or has makes the call site read as a claim: if (isValid(x)) is a sentence, which is exactly what a condition should be.Explain it
The short form is not just shorter.
Discussion prompt
A classmate writes if (x > 0) { return true; } else { return false; } and says it is clearer than return x > 0; because it spells out both cases. Give them a reason to prefer the short form that is not about typing less.
Hint: How many ways can each version be wrong?
Answer:
The long version has two places to make a mistake — you can swap the returns, or get the condition wrong. The short version has one. And a swapped pair of returns still compiles and still looks sensible, so nothing catches it.
It also says the same thing twice. The condition already tells you when the answer is true; the branches repeat that information in a form that can drift out of step with it.
The general principle is worth naming: prefer the form with fewer independent things to get right. It is the same argument as always writing braces in Lesson 5a, applied to a different construct.
Section
Sections 5.9-5.10
Concept
One of the most important tasks in any program is to validate input. People make mistakes while typing, especially on smartphones, and incorrect inputs may cause your program to fail. Worse, someone may intentionally try to break your system by entering unexpected input.
Scanner in = new Scanner(System.in);
System.out.print("Enter a number: ");
double x = in.nextDouble();
double y = Math.log(x);
System.out.println("The log is " + y);| what the user types | what happens |
|---|---|
| 100 | works — the log is about 4.6 |
| -1 | Math.log returns NaN — 'not a number' |
| 0 | NaN again — the logarithm is undefined at zero |
| hello | InputMismatchException — the program crashes |
Five lines of code, and two of the four inputs break it. You should never assume that users will input the right kind of data.
Notation
The two bad inputs fail in completely different ways, so they need completely different guards — and the order matters.
Annotate
hasNextDouble is a boolean method on Scanner that checks whether the next input can be interpreted as a double. It looks without consuming, so you can still read the value afterwards.nextDouble on the word 'hello' you get an InputMismatchException immediately — there is no value to range-check.in.next() returns only the next token, in contrast to in.nextLine which returns an entire line. It is used here to show the user exactly which word was not a number.System.err is an OutputStream for error messages and warnings. Some development environments display it in a different colour or a separate window.return exits main early, and returning from main terminates the program. That is what stops execution reaching the range check with no valid value to check.Two guards, in order: can it be read at all, and is the value usable. Nearly every input-validating program has that shape.
Worked example
What started as five lines at the beginning of Section 5.9 is now a 30-line program. Making programs robust, and secure, often requires a lot of additional checking.
import java.util.Scanner;
/**
* Demonstrates input validation using if statements.
*/
public class Logarithm {
public static void main(String[] args) {
// prompt for input
Scanner in = new Scanner(System.in);
System.out.print("Enter a number: ");
// check the format
if (!in.hasNextDouble()) {
String word = in.next();
System.err.println(word + " is not a number");
return;
}
// check the range
double x = in.nextDouble();
if (x > 0) {
double y = Math.log(x);
System.out.println("The log is " + y);
} else {
System.out.println("The log is undefined");
}
}
}| input | format check | range check | output |
|---|---|---|---|
| 100 | passes | 100 > 0 passes | The log is 4.605... |
| -1 | passes | -1 > 0 fails | The log is undefined |
| 0 | passes | 0 > 0 fails | The log is undefined |
| hello | fails | never reached | hello is not a number (to System.err) |
Prompt for input.
Why: The Scanner and the prompt, as in Chapter 3.
Check the format, and return if it fails.
Why: !in.hasNextDouble() reads as 'if there is not a double coming next'. The early return means the rest of main never runs.
Only now read the value.
Why: By this point nextDouble is guaranteed not to throw, because you have already asked whether it can succeed.
Check the range.
Why: In mathematics the natural logarithm is undefined for values at or below zero, and Java returns NaN — so guard against it and print a message instead.
Verify: Run it four times with 100, -1, 0 and hello, and check each row of the table.
Why: Notice that no input crashes the program any more. That is the standard the section is aiming at: a program you could hand to a stranger.
It is important to write comments every few lines. They help other people read your code and help you document what you are trying to do — and if there is a mistake, good comments make it much easier to find.
Prediction
The validated version of the program.
if (!in.hasNextDouble()) {
String word = in.next();
System.err.println(word + " is not a number");
return;
}
double x = in.nextDouble();| step | result |
|---|---|
| hasNextDouble() | false |
| !false | true — the branch runs |
| in.next() | reads the token "hello" |
| return | main ends; nextDouble is never reached |
Predict first
What does the program do?
Correct: Prints 'hello is not a number' to System.err and exits
Why: The format check catches the bad input before nextDouble is ever called, so no exception occurs. in.next() retrieves the offending token so it can be named in the message, and return exits main, which terminates the program.
Concept
Math.log(-1) does not throw an exception. It returns NaN, which stands for not a number — and NaN propagates silently through every calculation it touches.
| failure | how you find out | how bad |
|---|---|---|
| InputMismatchException | the program crashes with a stack trace | obvious, and immediate |
| Math.log of a negative | the answer is NaN | silent — it looks like a value |
| NaN used in arithmetic | every result becomes NaN | spreads through the whole computation |
| NaN printed | the output says NaN | the user sees it, eventually |
The second row is the more dangerous one, which is counter-intuitive. A crash tells you exactly where the problem is; NaN quietly contaminates everything downstream and only becomes visible at the very end, far from its cause.
Trap
The check comes too late. The exception has already been thrown.
double x = in.nextDouble(); // throws here if the input is "hello"
if (!in.hasNextDouble()) {
System.err.println("not a number");
return;
}| input | line 1 | line 2 reached? |
|---|---|---|
| 100 | reads 100 | yes — but pointless, the value is already read |
| hello | InputMismatchException | no — the program has crashed |
The guard is written after the thing it was supposed to guard. This is a very common shape of mistake, and it is invisible in testing if you only ever type valid input.
Check first, then read. hasNextDouble looks without consuming.
if (!in.hasNextDouble()) {
String word = in.next();
System.err.println(word + " is not a number");
return;
}
double x = in.nextDouble(); // now guaranteed safe| input | the check | nextDouble reached? |
|---|---|---|
| 100 | hasNextDouble is true | yes — reads 100 safely |
| hello | hasNextDouble is false | no — returned early |
The general shape is ask before you act. The has... methods exist precisely so that you can find out whether an operation will succeed before attempting it.
Definition probe
Two guards, two distinct kinds of bad input.
Sort into buckets
Sort each input by the check that stops it.
Error analysis
Five design decisions in thirty lines. Each was made for a reason.
Annotate
!in.hasNextDouble() — the negation puts the error case first and lets the normal path continue unindented afterwards. This shape is called a guard clause.in.next() rather than in.nextLine() — next returns only the offending token, so the message can name exactly which word was not a number.System.err rather than System.out — this is an error, and some environments colour or separate System.err so it stands out.return — leaves main immediately, which terminates the program. Without it, execution would continue to nextDouble with input that is known to be bad.Notice the asymmetry between the two checks. Bad format is unrecoverable and exits; bad range is a legitimate case with its own message. Deciding which of the two a failure is, is most of the design.
Real world
The has... then next... shape is a specific instance of a very general pattern.
Discussion prompt
The rule here is: ask whether an operation will succeed before attempting it. Where else, in programming or outside it, do you check first and act second — and what is the alternative?
Hint: The alternative has a name too.
Answer:
Everywhere something might not be there: checking a file exists before opening it, checking a list is not empty before taking its first element, checking a value is not null before using it — which is Chapter 9.
The alternative is to attempt it and handle the failure, which in Java means catching the exception. Chapter 15 introduces that. Both approaches are used in real code, and the choice depends on whether failure is expected or exceptional.
The reason to learn the check-first form now is that it uses only what you have: a boolean method and an if statement. Validation is not an advanced topic — it is an if statement placed before the thing that would break.
Comparison
Fill the blanks. The last column is the one people forget.
Comparison matrix
| operator | true when | false when | evaluates the right operand? |
|---|---|---|---|
| && | both sides are true | either side is false | only if the left is true |
| || | either side is true | both sides are false | only if the left is false |
| ! | its operand is false | its operand is true | it has only one operand |
The last column is short-circuit evaluation, and it is the reason count != 0 && total / count > 10 is safe while the reverse order crashes.
Pattern
Validating input is a guard clause followed by the normal path, and it is the shape most robust programs have.
// 1. guard: check the format, and leave if it is wrong
if (!in.hasNextDouble()) {
System.err.println(in.next() + " is not a number");
return;
}
// 2. now it is safe to read
double x = in.nextDouble();
// 3. check the range, and handle both cases
if (x > 0) {
...
} else {
System.out.println("undefined");
}| step | prevents |
|---|---|
| check the format first | InputMismatchException from nextDouble |
| return early | the rest of the method running with bad input |
| check the range | a silent NaN spreading through the calculation |
| put the guard on the left of && | an exception from the right operand |
Check
Work it out before you click.
int[] nothing = null;
int n = 0;
if (n > 0 && 10 / n > 2) {
System.out.println("yes");
}
System.out.println("done");| operand | value | evaluated? |
|---|---|---|
| n > 0 | false | yes |
| 10 / n > 2 | would throw | no |
| result | false | — |
Check your understanding
What does this display?
Answer: A
Why: The left operand of && is false, so the result is already decided and Java never evaluates the division. The if body is skipped and execution continues to the final println. Reversing the operands would produce a divide-by-zero exception instead.
Check
Work it out before you click.
Check your understanding
Which expression is equivalent to !(a >= 5 || b == 2)?
Answer: A
Why: Each term is negated — the opposite of >= is <, and the opposite of == is != — and the || becomes &&. Read aloud: not (a is at least 5 or b is 2) means a is below 5 and b is not 2.
>= is <, not <= — this version would wrongly include the case where a is exactly 5.Check
Work it out before you click.
public static boolean isEven(int n) {
return n % 2 == 0;
}
// in main:
if (isEven(7)) {
System.out.println("even");
} else {
System.out.println("odd");
}| step | value |
|---|---|
| 7 % 2 | 1 |
| 1 == 0 | false |
| isEven(7) | false |
Check your understanding
What does this display, and why is no == true needed?
isEven(7) == true would be clearerAnswer: A
Why: 7 is odd, so the method returns false and the else branch runs. The call needs no comparison because a boolean-returning method call is already a boolean expression — comparing it to true would add words without adding meaning.
n % 2 == 0 is false and the method returns false.Real world
Think Java raises this deliberately: someone may intentionally try to break into your system by entering unexpected inputs.
Discussion prompt
Your program reads a number and computes with it. What is the difference, from an attacker's point of view, between a program that crashes on bad input and one that validates it — and why might a crash be more useful to them than you would expect?
Hint: What does a stack trace contain?
Answer:
A crash produces a stack trace, which names your classes, methods, file names and line numbers. That is information about the internals of your program that an attacker did not previously have, handed over because of an unvalidated input.
It is also a denial of service in miniature: if any user can crash the program by typing a word, the program cannot be relied on. Both problems come from the same root — trusting input.
The habit worth forming now: treat every value that comes from outside your program as unvalidated until you have checked it. That includes keyboard input, files, and anything over a network. It is why five lines became thirty, and why that is a good trade.
Commit first
Commit to an answer and to your confidence.
Predict first
Is !(x == 0 || y == 0) the same as x != 0 || y != 0?
Correct: No — it is x != 0 && y != 0, because the operator must flip
Why: De Morgan's law requires two changes, not one: negate each term AND change || to &&. Check the case x = 0, y = 5 — the original condition is false there, and the incorrect version x != 0 || y != 0 is true, so they disagree. Forgetting to flip the operator is the single most common error with these laws, and it produces a condition that agrees with the intended one in some cases and not others.
Explain it
Two minutes, out loud.
Discussion prompt
Explain to a classmate why if (count != 0 && total / count > 10) does not crash when count is zero, and why writing the two conditions the other way round would. Then explain what that tells them about how && differs from and in mathematics.
Hint: The last part is the interesting one.
Answer:
Java evaluates the left side first. For &&, if the left is false the whole thing is false no matter what the right side says — so Java does not bother evaluating it. The division never happens.
Swap them and the division is now the left operand, so it runs first, and dividing by zero throws before the guard is ever reached.
And that means && is ordered in a way that mathematical 'and' is not. In mathematics A and B is the same as B and A. In Java they are logically the same but operationally different, and the left operand can protect the right one.
Exit ticket
One question before you close the deck.
Predict first
Why must the format check hasNextDouble() come before nextDouble() rather than after it?
Correct: Because nextDouble throws InputMismatchException on bad input, so checking afterwards is too late
Why: nextDouble throws immediately when the next token cannot be read as a double, so any check placed after it never runs. hasNextDouble looks at the input without consuming it, which is precisely what lets you ask 'will this succeed?' before committing. The general shape is ask before you act, and it applies to files, lists and null references as much as to Scanner.
Connect it up
One page, from memory.
Draw it
Draw a four-row truth table for A && B and A || B, then beside it write both of De Morgan's laws and use one of them to negate age >= 18 && hasTicket. Underneath, write the three-step validation skeleton — format guard, read, range check — and mark which run-time failure each step prevents. Finally, write one sentence explaining why count != 0 has to be the left operand of the &&.
Recap
Six sections that turn conditions from single comparisons into real logic, and then use that logic to make a program safe.
| if you remember one thing | it is this |
|---|---|
| about && | the left operand guards the right |
| about negation | flip each term and flip the operator |
| about input | ask before you act |
&&, || and ! combine boolean values; && needs both sides, || needs either, ! reverses one.! binds tighter than >, so negating a comparison needs parentheses.< is >=, not >.return exits a method early, and returning from main ends the program.Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.