1 of 20

Learning Java: �A Test-Driven Approach

Chapter 2.1: Conditionals

2 of 20

Conditional statements.

Conditionals are used for decision-making.

  • If something is true, we do one thing, and if it isn't true, we do something else.

p resolves to a boolean value.

  • Called the predicate.

The consequent is evaluated when p is true.

The alternative is evaluated when p is false.

if (p) {

// consequent

} else {

… // alternative

}

3 of 20

Conditional statements.

Example: consider the right-hand code.

42 is assigned to x if the predicate is true.

The predicate resolves to true, because y (42) is less than 200 / 3, which is 66 (integer division).

int x = 0;�int y = 42;�int z = 200;

if (y < z / 3) {

x = 42;

} else {

x = 84;

}

4 of 20

Conditional statements.

int x = 0;�int y = 42;�int z = 200;

if (z < 100) {

x = 42;

} else if (z < 200) {

x = 900;

} else if (z < 300) {

x = 2000;

} else {

x = 10000;

}

We can have multiple alternatives in an if statement.

Example: consider the code on the right.

The variable x takes on distinct values depending on the conditions.

If you're coming from Python, this is the same as if/elif/else.

If you're coming from Racket, this is the same as a cond.

5 of 20

Conditional example.

Let's design a method for determining the letter grade of a numeric grade.

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;

class ComputeLetterGradeTester {�

@Test

void testComputeLetterGrade() {

assertAll(

() -> assertEquals('A', computeLetterGrade(98)),

() -> assertEquals('B', computeLetterGrade(81)),

() -> assertEquals('C', computeLetterGrade(70)),

() -> assertEquals('D', computeLetterGrade(60)),

() -> assertEquals('F', computeLetterGrade(59))),

}�}

Remember: test cases should cover ALL possibilities!

6 of 20

Conditional example.

static char computeLetterGrade(int g) {

if (g >= 90) {

return 'A';

} else if (g >= 80) {

return 'B';

} else if (g >= 70) {

return 'C';

} else if (g >= 60) {

return 'D';

} else {

return 'F';

}

}

7 of 20

Conditional example.

static char computeLetterGrade(int g) {

if (g >= 90) {

return 'A';

} else if (g >= 80) {

return 'B';

} else if (g >= 70) {

return 'C';

} else if (g >= 60) {

return 'D';

} else {

return 'F';

}

}

static char computeLetterGrade(int g) {

char c = 'F';

if (g >= 90) { c = 'A'; }

if (g >= 80) { c = 'B'; }

if (g >= 70) { c = 'C'; }

if (g >= 60) { c = 'D'; }

else { c = 'F'; }

return c;

}

What is the difference between the two code examples?

The one on the bottom does not work! Consider an input of 96.

Sequential 'ifs' are mutually exclusive; else ifs only execute when the previous branch was not taken.

8 of 20

Conditional expression.

Remember, 'if' is a conditional statement! It cannot be returned itself.

  • E.g., return if (…) does not work.

One solution is the ternary operator/conditional expression!

  • Denoted with a ? and : symbol.

return p ? c : u is equivalent to: if (p) {

return c;

} else {

return u;

}

9 of 20

Conditional expression.

The conditional expression is nice when you want to assign values based on logical operations.

Example: assign, to x, 42 if the sum of y and z exceeds the square root of |y * z|, otherwise assign the minimum of w and u.

double x = (y + z) > Math.sqrt(Math.abs(y * z)) ? 42 : Math.min(w, u);

We could also do:�

if ((y + z) > Math.sqrt(Math.abs(y * z))) {

x = 42;

} else {

x = Math.min(w, u);

}

10 of 20

Conditional expression.

It is important to note that ternary expressions should be used sparingly!

While we could in theory write all if statements as ternary operations, doing so likely obfuscates the logic of our code.

char grade = (g >= 90) ? 'A' : (g >= 80) ? 'B' : (g >= 70) ? 'C' : (g >= 60) ? 'D' : 'F';

11 of 20

Logical operators over booleans.

We know that operators such as <, !=, and such are logical operators over numbers and characters.

Example: write code that assigns, to y, 100 if x is both positive and divisible by 2, otherwise assign 0.

That is very verbose!

Let's combine those two inner conditions

using a new logical operator.

int y = 0;

if (x > 0) {

if (x % 2 == 0) {

y = 100;

} else {

y = 0;

}

} else {

y = 0;

}

12 of 20

Logical operators over booleans.

Logical AND: &&

Logical OR: ||

Logical NOT: !

Example from before: y = x > 0 && x % 2 == 0 ? 100 : 0;

p && q is true if both p and q are expressions that resolve to true.

p || q is true if at least one of p or q is an expression that resolves to true.

!p flips the resulting evaluation of p, i.e., if evaluating p resolves to true, !p resolves to false, and vice versa.

13 of 20

Common mistake with && and ||.

As a warning, MANY programmers will accidentally omit a & or a | from the relation, typing expressions such as x & y or x | y.

& and | are bitwise operators; NOT logical operators!

We will not discuss these operators.

14 of 20

Short-circuiting.

Logical AND and OR are short-circuiting operators.

Logical AND only executes q if p is true. When p is false, it doesn't matter what q is. Both operands of AND must resolve to true.

Logical OR only executes q if p is false. When p is true, it doesn't matter what q is. Only one operand of OR must resolve to true.

15 of 20

Short-circuiting example.

Before we talk about short-circuiting in action, we need to explain four operators.

x++, x--, ++x, and --x.

These are the postfix increment, postfix decrement, prefix increment, and prefix decrement operators respectively.

Operated on l-values, or (left-hand) variables.

Example: x++ increments x and stores it back into x.

16 of 20

Short-circuiting example.

What do “prefix” and “postfix” have to do with this?

Example:

int x = 2;

int y = x++ + ++x;

// x++ USES x in the expression, then increments it.

// ++x increments x, and only then uses it in the

// expression.

// Resolves to 2 + 4 = 6. x was affected to be 4.

Example (using x and y from above):

int z = ++x * (x-- + --y);

// 5 * (x-- + --y) // prefix ++ happens before *.

// 5 * (5 + 5)

// 50

17 of 20

Short-circuiting example.

Consider the following sloppy code.

The first expression short-circuits because 0 is not less than 0. Therefore, the y on the right-hand side is not decremented.

The second expression also short-circuits because z is not equal to 100. Therefore, neither the x nor the y on the right-hand side are incremented.

Don't use prefix/postfix evaluations in conditionals.

int x = 0;

int y = 1;

int z = 2;

if (x < --y && --y == -1) {

z = 100;

}

if (z != 100 || y++ + ++x == 2) {

z = 200;

}

18 of 20

Negating expressions, DeMorgan's law.

Consider the following expression:

(x < y && y == -1)

What is its logical negation? That is, what is the result of:

!(x < y && y == -1)

Distribute the negation inward. Follow the logical negation table.

Operator

Negated Operator

<

>=

<=

>

>

<=

>=

<

==

!=

!=

=

&&

||

||

&&

19 of 20

Negating expressions, DeMorgan's law.

!(x < y && y == -1)

= !(x < y) || !(y == -1)

= (x >= y) || (y != -1)

DeMorgan's law:

!(P || Q) = (!P && !Q)

!(P && Q) = (!P || !Q)

Double negations cancel out.

!!P = P, !!(P && Q) = (P && Q)

Operator

Negated Operator

<

>=

<=

>

>

<=

>=

<

==

!=

!=

=

&&

||

||

&&

20 of 20

Negating expressions, DeMorgan's law.

Study the table - do not get the operators mixed up!

Many programmers think the logical negation of < is >, but that's not true!

Think of it like this: if x < 100, then the negation CANNOT be x > 100, because that excludes 100. Therefore, the negation of x < 100 is x >= 100.

Operator

Negated Operator

<

>=

<=

>

>

<=

>=

<

==

!=

!=

=

&&

||

||

&&