Logical Operators, Booleans, and Validating Input

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

What this lesson covers

The lesson, slide by slide

1. Logical Operators, Booleans, and Validating Input

Title

Think Java 2e · Chapter 5 · Conditionals and Logic

Sections 5.5-5.10 · pp. 77-84

2. What you will be able to do

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.

3. Retrieve before you read

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.

4. From one condition to real logic

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.

5. Logical operators

Section

Section 5.5

6. and, or, not

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
expressiontrue whenfalse when
A && Bboth A and B are trueeither one is false
A || Beither A or B is trueboth are false
!AA is falseA 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.

7. Collapsing nested and chained conditionals

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.
  • The rewrite only works when the bodies are identical. If the two branches did different things, they could not be combined into one block — which is worth checking before you reach for the operator.
  • It is useful to explore different ways of representing the same logic, especially when it is complex. The version with fewer levels is usually, but not always, the clearer one.

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.

8. Short-circuit evaluation

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");
}
countfirst operandis the second evaluated?result
00 != 0 is falseno — short circuitfalse, and no division happens
44 != 0 is trueyesdepends 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.

9. Is the second operand evaluated?

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");
}
operandvalueevaluated?
count != 0falseyes
total / count > 10would throwno — short circuit

Predict first

What happens when this runs?

  • Nothing is printed, and no exception occurs
  • An ArithmeticException — division by zero
  • high is printed
  • A compile error

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.

10. Precedence among the logical operators

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.

writtengrouped asnote
x > 0 && x < 10(x > 0) && (x < 10)relational before logical, so no parentheses needed
!x > 0(!x) > 0an error — ! binds tighter than >
!(x > 0)not (x greater than 0)the parentheses are required
a || b && ca || (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.

11. Writing a range test the way you say it

Trap

The trap

The mathematical form, which is not Java.

if (0 < x < 10) {          // does not compile
    System.out.println("in range");
}
stepwhat Java doesresult
0 < xevaluates to a booleantrue or false
(that boolean) < 10compare a boolean with an intcompile 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.

The fix

Two separate comparisons, joined with &&.

if (0 < x && x < 10) {
    System.out.println("in range");
}
partvalue for x = 5
0 < xtrue
x < 10true
true && truetrue

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.

12. Which operator does the job?

Definition probe

Read each requirement in English and choose the operator.

Sort into buckets

Sort each requirement.

&&
the value is between 1 and 10 inclusive; the user is an admin and is logged in
||
the value is outside the range 1 to 10; the file is missing or empty
!
the answer is not yes
and
Both conditions must hold at once, so the expression is true only when neither part fails.
or
Either condition is enough on its own, so the expression is false only when both parts fail.
not
A single condition is being reversed — true becomes false and false becomes true.

13. Write a range test

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.

14. When does the order of operands matter?

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.

15. De Morgan's laws

Section

Section 5.6

16. Negate each term and change the operator

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");
}
stepexpression
start!(x == 0 || y == 0)
negate each term!(x == 0) ... !(y == 0)
change the operator|| becomes &&
simplify the negationsx != 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.

17. Negating relational operators

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

  • The opposite of < 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.
  • Read them out loud in English. If I do not want the case where x is less than 5 and y is 3, then I need x to be greater than or equal to 5, or I need y to be anything but 3.
  • Notice the operator always flips. An && 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.

18. Applying the law step by step

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
agehasTicketoriginalnegation
20truetruefalse
20falsefalsetrue
15truefalsetrue
15falsefalsetrue

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.

19. What is the negation?

Prediction

Apply the law mechanically.

// negate this condition:
x > 10 && y < 5
stepresult
negate x > 10x <= 10
negate y < 5y >= 5
change && to ||x <= 10 || y >= 5

Predict first

Which expression is equivalent to !(x > 10 && y < 5)?

  • x <= 10 || y >= 5
  • x < 10 || y > 5
  • x <= 10 && y >= 5
  • 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.

20. Why bother, when ! works?

Concept

!(A && B) is perfectly correct Java. The argument for rewriting it is entirely about the person reading it next.

written with !after De Morganwhich reads better aloud
!(x == 0 || y == 0)x != 0 && y != 0the second — 'x is not zero and y is not zero'
!(a && b)!a || !busually the second, if a and b have good names
!(score >= 50)score < 50the second — 'the score is below 50'
!found!foundthe 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.

21. Forgetting to change the operator

Trap

The trap

Negating each term and leaving the && alone.

// intended: NOT (in stock AND affordable)
if (!inStock && !affordable) {      // wrong
    System.out.println("cannot buy");
}
inStockaffordableshould be true?this condition
truetruefalsefalse — correct
truefalsetruefalse — WRONG
falsetruetruefalse — WRONG
falsefalsetruetrue — 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.

The fix

Negate each term AND change the operator.

// NOT (inStock && affordable)  ->  !inStock || !affordable
if (!inStock || !affordable) {
    System.out.println("cannot buy");
}
inStockaffordableshould be true?this condition
truetruefalsefalse
truefalsetruetrue
falsetruetruetrue
falsefalsetruetrue

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.

22. Match each condition to its negation

Matching

Four negations, done correctly.

Match the pairs

  • a. x < 5
  • b. x >= 1 || y != 7
  • c. a && b
  • d. x == 0 || y == 0
  • r1. x >= 5
  • r2. x < 1 && y == 7
  • r3. !a || !b
  • r4. x != 0 && y != 0

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.

23. Which negation is wrong?

Elimination

Three of these apply the law correctly.

Eliminate the wrong options

Rule out the three correct ones and keep the mistake.

  • A. !(a || b) is !a && !b
  • B. !(x > 3) is x <= 3
  • C. !(a && b) is !a && !b
  • D. !(x != 2) is x == 2

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.

24. Say De Morgan's law in English

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.

25. Boolean variables and flags

Section

Section 5.7

26. Store a true or false value in a variable

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
linekind 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.

27. A flag is a condition you have named

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.

28. Never compare a boolean to true

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");
}
writtencorrect?reads as
if (evenFlag)yesif even flag
if (evenFlag == true)works, but verboseif even flag is equal to true
if (!evenFlag)yesif not even flag
if (evenFlag == false)works, but verboseif 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.

29. What does this print?

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");
}
stepvalue
x % 21
1 == 0false
isEvenfalse
!isEventrue

Predict first

What is displayed?

  • odd
  • even
  • nothing
  • a compile error

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.

30. Naming a flag well

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 namebetter namewhy
flagisEvensays what the condition is
bhasTicketreads as a claim in a condition
checkinputIsValida noun phrase that can be true or false
notFoundfoundavoids 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.

31. Comparing a boolean to true

Trap

The 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");
}
writtenin Javain C
evenFlag == trueworks, verboseworks, verbose
evenFlag = truecompile 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.

The fix

Use the flag directly. The mistake becomes impossible to write.

if (evenFlag) {
    System.out.println("even");
}

if (!evenFlag) {
    System.out.println("odd");
}
goalwrite
act when the flag is trueif (flag)
act when the flag is falseif (!flag)
neverif (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.

32. Well-named flag or not?

Definition probe

A flag's name should read as a claim that can be true or false.

Sort into buckets

Sort each flag name.

reads well in a condition
isEmpty; hasNextDouble
poor name
flag2; notValid; check
good
It reads as a claim — if (isEmpty) is a sentence — and negating it with ! still reads naturally.
poor
Either it says nothing about the condition, or it contains a negative so that !notValid becomes a double negative the reader has to unpick.

33. Store and use a flag

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.

34. When is a flag worth the extra line?

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.

35. Boolean methods

Section

Section 5.8

36. A method can return true or false

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;
    }
}
xthe conditionreturns
22 > -10 && 2 < 10true
1717 > -10 && 17 < 10false
-5-5 > -10 && -5 < 10true
-42-42 > -10 is falsefalse

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.

37. The same method, written twice

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.
  • The long version is a common beginner shape: if (condition) return true; else return false; always simplifies to return condition;.
  • It is not merely shorter. The long version invites a bug — swapping the two returns, or getting the branches out of step — that the short version cannot have.
  • The pattern generalises. Whenever you find yourself returning 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.

38. Using a boolean method as a condition

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");
}
usagewhat happensresult
println(isSingleDigit(2))the call returns true, println displays ittrue
!isSingleDigit(17)returns false, then negatedbigFlag is true
if (isSingleDigit(z))the returned boolean IS the conditionbranches 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.

39. Simplify a boolean method

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.

40. Where you have already seen this

Concept

Boolean methods are not a new idea in the library. You are about to use one that has existed all along.

methodbelongs toasks
in.hasNextDouble()Scannercan the next input be read as a double?
in.hasNextInt()Scannercan it be read as an int?
in.hasNextLine()Scanneris there another line?
isSingleDigit(x)yoursis 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.

41. if (condition) return true; else return false;

Trap

The 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;
    }
}
xshould returnactually returns
5truefalse
-5falsetrue
0falsetrue

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.

The fix

Return the condition itself. There is nothing left to get backwards.

public static boolean isPositive(int x) {
    return x > 0;
}
xx > 0returns
5truetrue
-5falsefalse
0falsefalse

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.

42. What does this display?

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));
callconditionreturns
isSingleDigit(2)2 > -10 && 2 < 10true
isSingleDigit(17)17 < 10 is falsefalse

Predict first

What are the two lines of output?

  • true then false
  • false then true
  • true then true
  • 2 then 17

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.

43. Good name for a boolean method?

Discrimination

A boolean method's name should sound like a yes/no question.

Sort into buckets

Sort each method name.

reads as a question
isSingleDigit; hasNextDouble; isValid
does not
checkDigit; digitCheck
good
Beginning with 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.
poor
The name describes an action rather than a question, so a reader cannot tell whether it returns an answer, performs a check and prints something, or both.

44. Explain why the long form is worse

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.

45. Validating input

Section

Sections 5.9-5.10

46. Never assume the user types what you expect

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 typeswhat happens
100works — the log is about 4.6
-1Math.log returns NaN — 'not a number'
0NaN again — the logarithm is undefined at zero
helloInputMismatchException — 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.

47. Two different checks, for two different failures

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.
  • The format check has to come first. If you call 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.

48. The complete program

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");
        }
    }
}
inputformat checkrange checkoutput
100passes100 > 0 passesThe log is 4.605...
-1passes-1 > 0 failsThe log is undefined
0passes0 > 0 failsThe log is undefined
hellofailsnever reachedhello 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.

49. What happens with input 'hello'?

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();
stepresult
hasNextDouble()false
!falsetrue — the branch runs
in.next()reads the token "hello"
returnmain ends; nextDouble is never reached

Predict first

What does the program do?

  • Prints 'hello is not a number' to System.err and exits
  • Throws an InputMismatchException
  • Prints 'The log is NaN'
  • Waits for more input

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.

50. NaN, and why a bad value is worse than a crash

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.

failurehow you find outhow bad
InputMismatchExceptionthe program crashes with a stack traceobvious, and immediate
Math.log of a negativethe answer is NaNsilent — it looks like a value
NaN used in arithmeticevery result becomes NaNspreads through the whole computation
NaN printedthe output says NaNthe 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.

51. Reading the input before checking it

Trap

The 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;
}
inputline 1line 2 reached?
100reads 100yes — but pointless, the value is already read
helloInputMismatchExceptionno — 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.

The fix

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
inputthe checknextDouble reached?
100hasNextDouble is trueyes — reads 100 safely
hellohasNextDouble is falseno — 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.

52. Which check catches which failure?

Definition probe

Two guards, two distinct kinds of bad input.

Sort into buckets

Sort each input by the check that stops it.

the format check — hasNextDouble
the user types 'hello'; the user types an empty line then a word
the range check — x > 0
the user types -1; the user types 0
fmt
The input cannot be interpreted as a double at all, so reading it would throw InputMismatchException. This check must come first.
rng
The input is a perfectly good double; it is just not a value the calculation can use. Math.log would return NaN rather than throwing.

53. Annotate the validated program

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.
  • The range check is if-else, not a second guard — because a non-positive number is not an error the program cannot handle; it just has a different answer, so both branches produce output.

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.

54. Where else do you validate before acting?

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.

55. The three logical operators

Comparison

Fill the blanks. The last column is the one people forget.

Comparison matrix

operatortrue whenfalse whenevaluates the right operand?
&&both sides are trueeither side is falseonly if the left is true
||either side is trueboth sides are falseonly if the left is false
!its operand is falseits operand is trueit 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.

56. The pattern to carry away

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");
}
stepprevents
check the format firstInputMismatchException from nextDouble
return earlythe rest of the method running with bad input
check the rangea silent NaN spreading through the calculation
put the guard on the left of &&an exception from the right operand

57. Check: short circuit

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");
operandvalueevaluated?
n > 0falseyes
10 / n > 2would throwno
resultfalse—

Check your understanding

What does this display?

  • A. done (correct)
  • B. yes then done
  • C. an ArithmeticException
  • D. nothing

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.

Why B tempts people
The condition is false, so the body does not run — n is 0, which is not greater than 0.
Why C tempts people
Short-circuit evaluation means the division is never performed at all, so it cannot throw.
Why D tempts people
The final println is outside the if statement and runs regardless of the condition.

58. Check: De Morgan

Check

Work it out before you click.

Check your understanding

Which expression is equivalent to !(a >= 5 || b == 2)?

  • A. a < 5 && b != 2 (correct)
  • B. a < 5 || b != 2
  • C. a <= 5 && b != 2
  • D. 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.

Why B tempts people
This negates the terms correctly but leaves the || in place; De Morgan requires the connective to flip as well.
Why C tempts people
The opposite of >= is <, not <= — this version would wrongly include the case where a is exactly 5.
Why D tempts people
This negates neither term correctly and keeps the wrong connective, so it is true in almost the opposite set of cases.

59. Check: boolean methods

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");
}
stepvalue
7 % 21
1 == 0false
isEven(7)false

Check your understanding

What does this display, and why is no == true needed?

  • A. odd — the method returns a boolean, which is already a condition (correct)
  • B. even — the method returns a boolean, which is already a condition
  • C. odd — but isEven(7) == true would be clearer
  • D. a compile error, because a method call cannot be a condition

Answer: 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.

Why B tempts people
7 divided by 2 leaves a remainder of 1, so the condition n % 2 == 0 is false and the method returns false.
Why C tempts people
The output is right but the reasoning is not: comparing a boolean to true is the verbose form the book explicitly advises against.
Why D tempts people
A method call that returns a boolean is a perfectly valid condition, and using one is the whole point of writing boolean methods.

60. Validation is a security question

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.

61. How sure are you?

Commit first

Commit to an answer and to your confidence.

Predict first

Is !(x == 0 || y == 0) the same as x != 0 || y != 0?

  • No — it is x != 0 && y != 0, because the operator must flip
  • Yes — negating each term is all that is required
  • No — it is !x == 0 && !y == 0
  • Yes, but only when x and y are both non-zero

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.

62. Explain it to someone else

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.

63. Exit ticket

Exit ticket

One question before you close the deck.

Predict first

Why must the format check hasNextDouble() come before nextDouble() rather than after it?

  • Because nextDouble throws InputMismatchException on bad input, so checking afterwards is too late
  • Because hasNextDouble consumes the input
  • Because Java requires boolean methods to be called first
  • Because nextDouble returns NaN for non-numeric input

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.

64. Draw the whole lesson

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 &&.

65. Recap

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 thingit is this
about &&the left operand guards the right
about negationflip each term and flip the operator
about inputask before you act

Sources

  1. 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
  2. The Java Tutorials — Equality, Relational, and Conditional Operators
  3. Think Java 2e — free online edition and source code

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

Book on Wyzant · Text (657) 465-8108